summaryrefslogtreecommitdiffstats
path: root/drivers/iio/potentiometer
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/iio/potentiometer
parentInitial commit. (diff)
downloadlinux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz
linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/iio/potentiometer')
-rw-r--r--drivers/iio/potentiometer/Kconfig149
-rw-r--r--drivers/iio/potentiometer/Makefile18
-rw-r--r--drivers/iio/potentiometer/ad5110.c344
-rw-r--r--drivers/iio/potentiometer/ad5272.c229
-rw-r--r--drivers/iio/potentiometer/ds1803.c264
-rw-r--r--drivers/iio/potentiometer/max5432.c133
-rw-r--r--drivers/iio/potentiometer/max5481.c194
-rw-r--r--drivers/iio/potentiometer/max5487.c157
-rw-r--r--drivers/iio/potentiometer/mcp4018.c188
-rw-r--r--drivers/iio/potentiometer/mcp41010.c202
-rw-r--r--drivers/iio/potentiometer/mcp4131.c492
-rw-r--r--drivers/iio/potentiometer/mcp4531.c399
-rw-r--r--drivers/iio/potentiometer/tpl0102.c172
-rw-r--r--drivers/iio/potentiometer/x9250.c220
14 files changed, 3161 insertions, 0 deletions
diff --git a/drivers/iio/potentiometer/Kconfig b/drivers/iio/potentiometer/Kconfig
new file mode 100644
index 0000000000..e6a9a3c678
--- /dev/null
+++ b/drivers/iio/potentiometer/Kconfig
@@ -0,0 +1,149 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Potentiometer drivers
+#
+# When adding new entries keep the list in alphabetical order
+
+menu "Digital potentiometers"
+
+config AD5110
+ tristate "Analog Devices AD5110 and similar Digital Potentiometer driver"
+ depends on I2C
+ help
+ Say yes here to build support for the Analog Devices AD5110, AD5112
+ and AD5114 digital potentiometer chip.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad5110.
+
+config AD5272
+ tristate "Analog Devices AD5272 and similar Digital Potentiometer driver"
+ depends on I2C
+ help
+ Say yes here to build support for the Analog Devices AD5272 and AD5274
+ digital potentiometer chip.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad5272.
+
+config DS1803
+ tristate "Maxim Integrated DS1803 and similar Digital Potentiometer driver"
+ depends on I2C
+ help
+ Say yes here to build support for the Maxim Integrated DS1803 and
+ DS3502 digital potentiometer chip.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ds1803.
+
+config MAX5432
+ tristate "Maxim MAX5432-MAX5435 Digital Potentiometer driver"
+ depends on I2C
+ help
+ Say yes here to build support for the Maxim
+ MAX5432, MAX5433, MAX5434 and MAX5435 digital
+ potentiometer chips.
+
+ To compile this driver as a module, choose M here: the
+ module will be called max5432.
+
+config MAX5481
+ tristate "Maxim MAX5481-MAX5484 Digital Potentiometer driver"
+ depends on SPI
+ help
+ Say yes here to build support for the Maxim
+ MAX5481, MAX5482, MAX5483, MAX5484 digital potentiometer
+ chips.
+
+ To compile this driver as a module, choose M here: the
+ module will be called max5481.
+
+config MAX5487
+ tristate "Maxim MAX5487/MAX5488/MAX5489 Digital Potentiometer driver"
+ depends on SPI
+ help
+ Say yes here to build support for the Maxim
+ MAX5487, MAX5488, MAX5489 digital potentiometer
+ chips.
+
+ To compile this driver as a module, choose M here: the
+ module will be called max5487.
+
+config MCP4018
+ tristate "Microchip MCP4017/18/19 Digital Potentiometer driver"
+ depends on I2C
+ help
+ Say yes here to build support for the Microchip
+ MCP4017, MCP4018, MCP4019
+ digital potentiometer chips.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mcp4018.
+
+config MCP4131
+ tristate "Microchip MCP413X/414X/415X/416X/423X/424X/425X/426X Digital Potentiometer driver"
+ depends on SPI
+ help
+ Say yes here to build support for the Microchip
+ MCP4131, MCP4132,
+ MCP4141, MCP4142,
+ MCP4151, MCP4152,
+ MCP4161, MCP4162,
+ MCP4231, MCP4232,
+ MCP4241, MCP4242,
+ MCP4251, MCP4252,
+ MCP4261, MCP4262,
+ digital potentiometer chips.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mcp4131.
+
+config MCP4531
+ tristate "Microchip MCP45xx/MCP46xx Digital Potentiometer driver"
+ depends on I2C
+ help
+ Say yes here to build support for the Microchip
+ MCP4531, MCP4532, MCP4541, MCP4542,
+ MCP4551, MCP4552, MCP4561, MCP4562,
+ MCP4631, MCP4632, MCP4641, MCP4642,
+ MCP4651, MCP4652, MCP4661, MCP4662
+ digital potentiometer chips.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mcp4531.
+
+config MCP41010
+ tristate "Microchip MCP41xxx/MCP42xxx Digital Potentiometer driver"
+ depends on SPI
+ help
+ Say yes here to build support for the Microchip
+ MCP41010, MCP41050, MCP41100,
+ MCP42010, MCP42050, MCP42100
+ digital potentiometer chips.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mcp41010.
+
+config TPL0102
+ tristate "Texas Instruments digital potentiometer driver"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say yes here to build support for the Texas Instruments
+ TPL0102, TPL0402
+ digital potentiometer chips.
+
+ To compile this driver as a module, choose M here: the
+ module will be called tpl0102.
+
+config X9250
+ tristate "Renesas X9250 quad controlled potentiometers"
+ depends on SPI
+ help
+ Enable support for the Renesas X9250 quad controlled
+ potentiometers.
+
+ To compile this driver as a module, choose M here: the module
+ will be called x9250.
+
+endmenu
diff --git a/drivers/iio/potentiometer/Makefile b/drivers/iio/potentiometer/Makefile
new file mode 100644
index 0000000000..d11fb73917
--- /dev/null
+++ b/drivers/iio/potentiometer/Makefile
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for industrial I/O potentiometer drivers
+#
+
+# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_AD5110) += ad5110.o
+obj-$(CONFIG_AD5272) += ad5272.o
+obj-$(CONFIG_DS1803) += ds1803.o
+obj-$(CONFIG_MAX5432) += max5432.o
+obj-$(CONFIG_MAX5481) += max5481.o
+obj-$(CONFIG_MAX5487) += max5487.o
+obj-$(CONFIG_MCP4018) += mcp4018.o
+obj-$(CONFIG_MCP4131) += mcp4131.o
+obj-$(CONFIG_MCP4531) += mcp4531.o
+obj-$(CONFIG_MCP41010) += mcp41010.o
+obj-$(CONFIG_TPL0102) += tpl0102.o
+obj-$(CONFIG_X9250) += x9250.o
diff --git a/drivers/iio/potentiometer/ad5110.c b/drivers/iio/potentiometer/ad5110.c
new file mode 100644
index 0000000000..991e745c4f
--- /dev/null
+++ b/drivers/iio/potentiometer/ad5110.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Analog Devices AD5110 digital potentiometer driver
+ *
+ * Copyright (C) 2021 Mugilraj Dhavachelvan <dmugil2000@gmail.com>
+ *
+ * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/AD5110_5112_5114.pdf
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+/* AD5110 commands */
+#define AD5110_EEPROM_WR 1
+#define AD5110_RDAC_WR 2
+#define AD5110_SHUTDOWN 3
+#define AD5110_RESET 4
+#define AD5110_RDAC_RD 5
+#define AD5110_EEPROM_RD 6
+
+/* AD5110_EEPROM_RD data */
+#define AD5110_WIPER_POS 0
+#define AD5110_RESISTOR_TOL 1
+
+#define AD5110_WIPER_RESISTANCE 70
+
+struct ad5110_cfg {
+ int max_pos;
+ int kohms;
+ int shift;
+};
+
+enum ad5110_type {
+ AD5110_10,
+ AD5110_80,
+ AD5112_05,
+ AD5112_10,
+ AD5112_80,
+ AD5114_10,
+ AD5114_80,
+};
+
+static const struct ad5110_cfg ad5110_cfg[] = {
+ [AD5110_10] = { .max_pos = 128, .kohms = 10 },
+ [AD5110_80] = { .max_pos = 128, .kohms = 80 },
+ [AD5112_05] = { .max_pos = 64, .kohms = 5, .shift = 1 },
+ [AD5112_10] = { .max_pos = 64, .kohms = 10, .shift = 1 },
+ [AD5112_80] = { .max_pos = 64, .kohms = 80, .shift = 1 },
+ [AD5114_10] = { .max_pos = 32, .kohms = 10, .shift = 2 },
+ [AD5114_80] = { .max_pos = 32, .kohms = 80, .shift = 2 },
+};
+
+struct ad5110_data {
+ struct i2c_client *client;
+ s16 tol; /* resistor tolerance */
+ bool enable;
+ struct mutex lock;
+ const struct ad5110_cfg *cfg;
+ /*
+ * DMA (thus cache coherency maintenance) may require the
+ * transfer buffers to live in their own cache lines.
+ */
+ u8 buf[2] __aligned(IIO_DMA_MINALIGN);
+};
+
+static const struct iio_chan_spec ad5110_channels[] = {
+ {
+ .type = IIO_RESISTANCE,
+ .output = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_ENABLE),
+ },
+};
+
+static int ad5110_read(struct ad5110_data *data, u8 cmd, int *val)
+{
+ int ret;
+
+ mutex_lock(&data->lock);
+ data->buf[0] = cmd;
+ data->buf[1] = *val;
+
+ ret = i2c_master_send_dmasafe(data->client, data->buf, sizeof(data->buf));
+ if (ret < 0) {
+ goto error;
+ } else if (ret != sizeof(data->buf)) {
+ ret = -EIO;
+ goto error;
+ }
+
+ ret = i2c_master_recv_dmasafe(data->client, data->buf, 1);
+ if (ret < 0) {
+ goto error;
+ } else if (ret != 1) {
+ ret = -EIO;
+ goto error;
+ }
+
+ *val = data->buf[0];
+ ret = 0;
+
+error:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static int ad5110_write(struct ad5110_data *data, u8 cmd, u8 val)
+{
+ int ret;
+
+ mutex_lock(&data->lock);
+ data->buf[0] = cmd;
+ data->buf[1] = val;
+
+ ret = i2c_master_send_dmasafe(data->client, data->buf, sizeof(data->buf));
+ if (ret < 0) {
+ goto error;
+ } else if (ret != sizeof(data->buf)) {
+ ret = -EIO;
+ goto error;
+ }
+
+ ret = 0;
+
+error:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static int ad5110_resistor_tol(struct ad5110_data *data, u8 cmd, int val)
+{
+ int ret;
+
+ ret = ad5110_read(data, cmd, &val);
+ if (ret)
+ return ret;
+
+ data->tol = data->cfg->kohms * (val & GENMASK(6, 0)) * 10 / 8;
+ if (!(val & BIT(7)))
+ data->tol *= -1;
+
+ return 0;
+}
+
+static ssize_t store_eeprom_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct ad5110_data *data = iio_priv(indio_dev);
+ int val = AD5110_WIPER_POS;
+ int ret;
+
+ ret = ad5110_read(data, AD5110_EEPROM_RD, &val);
+ if (ret)
+ return ret;
+
+ val = val >> data->cfg->shift;
+ return iio_format_value(buf, IIO_VAL_INT, 1, &val);
+}
+
+static ssize_t store_eeprom_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct ad5110_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = ad5110_write(data, AD5110_EEPROM_WR, 0);
+ if (ret) {
+ dev_err(&data->client->dev, "RDAC to EEPROM write failed\n");
+ return ret;
+ }
+
+ /* The storing of EEPROM data takes approximately 18 ms. */
+ msleep(20);
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR_RW(store_eeprom, 0);
+
+static struct attribute *ad5110_attributes[] = {
+ &iio_dev_attr_store_eeprom.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group ad5110_attribute_group = {
+ .attrs = ad5110_attributes,
+};
+
+static int ad5110_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct ad5110_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = ad5110_read(data, AD5110_RDAC_RD, val);
+ if (ret)
+ return ret;
+
+ *val = *val >> data->cfg->shift;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_OFFSET:
+ *val = AD5110_WIPER_RESISTANCE * data->cfg->max_pos;
+ *val2 = 1000 * data->cfg->kohms + data->tol;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 1000 * data->cfg->kohms + data->tol;
+ *val2 = data->cfg->max_pos;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_CHAN_INFO_ENABLE:
+ *val = data->enable;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad5110_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct ad5110_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (val > data->cfg->max_pos || val < 0)
+ return -EINVAL;
+
+ return ad5110_write(data, AD5110_RDAC_WR, val << data->cfg->shift);
+ case IIO_CHAN_INFO_ENABLE:
+ if (val < 0 || val > 1)
+ return -EINVAL;
+ if (data->enable == val)
+ return 0;
+ ret = ad5110_write(data, AD5110_SHUTDOWN, val ? 0 : 1);
+ if (ret)
+ return ret;
+ data->enable = val;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info ad5110_info = {
+ .read_raw = ad5110_read_raw,
+ .write_raw = ad5110_write_raw,
+ .attrs = &ad5110_attribute_group,
+};
+
+#define AD5110_COMPATIBLE(of_compatible, cfg) { \
+ .compatible = of_compatible, \
+ .data = &ad5110_cfg[cfg], \
+}
+
+static const struct of_device_id ad5110_of_match[] = {
+ AD5110_COMPATIBLE("adi,ad5110-10", AD5110_10),
+ AD5110_COMPATIBLE("adi,ad5110-80", AD5110_80),
+ AD5110_COMPATIBLE("adi,ad5112-05", AD5112_05),
+ AD5110_COMPATIBLE("adi,ad5112-10", AD5112_10),
+ AD5110_COMPATIBLE("adi,ad5112-80", AD5112_80),
+ AD5110_COMPATIBLE("adi,ad5114-10", AD5114_10),
+ AD5110_COMPATIBLE("adi,ad5114-80", AD5114_80),
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad5110_of_match);
+
+static const struct i2c_device_id ad5110_id[] = {
+ { "ad5110-10", AD5110_10 },
+ { "ad5110-80", AD5110_80 },
+ { "ad5112-05", AD5112_05 },
+ { "ad5112-10", AD5112_10 },
+ { "ad5112-80", AD5112_80 },
+ { "ad5114-10", AD5114_10 },
+ { "ad5114-80", AD5114_80 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ad5110_id);
+
+static int ad5110_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct iio_dev *indio_dev;
+ struct ad5110_data *data;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ mutex_init(&data->lock);
+ data->enable = 1;
+ data->cfg = device_get_match_data(dev);
+
+ /* refresh RDAC register with EEPROM */
+ ret = ad5110_write(data, AD5110_RESET, 0);
+ if (ret) {
+ dev_err(dev, "Refresh RDAC with EEPROM failed\n");
+ return ret;
+ }
+
+ ret = ad5110_resistor_tol(data, AD5110_EEPROM_RD, AD5110_RESISTOR_TOL);
+ if (ret) {
+ dev_err(dev, "Read resistor tolerance failed\n");
+ return ret;
+ }
+
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &ad5110_info;
+ indio_dev->channels = ad5110_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ad5110_channels);
+ indio_dev->name = client->name;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static struct i2c_driver ad5110_driver = {
+ .driver = {
+ .name = "ad5110",
+ .of_match_table = ad5110_of_match,
+ },
+ .probe = ad5110_probe,
+ .id_table = ad5110_id,
+};
+module_i2c_driver(ad5110_driver);
+
+MODULE_AUTHOR("Mugilraj Dhavachelvan <dmugil2000@gmail.com>");
+MODULE_DESCRIPTION("AD5110 digital potentiometer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/potentiometer/ad5272.c b/drivers/iio/potentiometer/ad5272.c
new file mode 100644
index 0000000000..b17941e4c2
--- /dev/null
+++ b/drivers/iio/potentiometer/ad5272.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Analog Devices AD5272 digital potentiometer driver
+ * Copyright (C) 2018 Phil Reid <preid@electromag.com.au>
+ *
+ * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/AD5272_5274.pdf
+ *
+ * DEVID #Wipers #Positions Resistor Opts (kOhm) i2c address
+ * ad5272 1 1024 20, 50, 100 01011xx
+ * ad5274 1 256 20, 100 01011xx
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+
+#define AD5272_RDAC_WR 1
+#define AD5272_RDAC_RD 2
+#define AD5272_RESET 4
+#define AD5272_CTL 7
+
+#define AD5272_RDAC_WR_EN BIT(1)
+
+struct ad5272_cfg {
+ int max_pos;
+ int kohms;
+ int shift;
+};
+
+enum ad5272_type {
+ AD5272_020,
+ AD5272_050,
+ AD5272_100,
+ AD5274_020,
+ AD5274_100,
+};
+
+static const struct ad5272_cfg ad5272_cfg[] = {
+ [AD5272_020] = { .max_pos = 1024, .kohms = 20 },
+ [AD5272_050] = { .max_pos = 1024, .kohms = 50 },
+ [AD5272_100] = { .max_pos = 1024, .kohms = 100 },
+ [AD5274_020] = { .max_pos = 256, .kohms = 20, .shift = 2 },
+ [AD5274_100] = { .max_pos = 256, .kohms = 100, .shift = 2 },
+};
+
+struct ad5272_data {
+ struct i2c_client *client;
+ struct mutex lock;
+ const struct ad5272_cfg *cfg;
+ u8 buf[2] __aligned(IIO_DMA_MINALIGN);
+};
+
+static const struct iio_chan_spec ad5272_channel = {
+ .type = IIO_RESISTANCE,
+ .output = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+};
+
+static int ad5272_write(struct ad5272_data *data, int reg, int val)
+{
+ int ret;
+
+ data->buf[0] = (reg << 2) | ((val >> 8) & 0x3);
+ data->buf[1] = (u8)val;
+
+ mutex_lock(&data->lock);
+ ret = i2c_master_send(data->client, data->buf, sizeof(data->buf));
+ mutex_unlock(&data->lock);
+ return ret < 0 ? ret : 0;
+}
+
+static int ad5272_read(struct ad5272_data *data, int reg, int *val)
+{
+ int ret;
+
+ data->buf[0] = reg << 2;
+ data->buf[1] = 0;
+
+ mutex_lock(&data->lock);
+ ret = i2c_master_send(data->client, data->buf, sizeof(data->buf));
+ if (ret < 0)
+ goto error;
+
+ ret = i2c_master_recv(data->client, data->buf, sizeof(data->buf));
+ if (ret < 0)
+ goto error;
+
+ *val = ((data->buf[0] & 0x3) << 8) | data->buf[1];
+ ret = 0;
+error:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static int ad5272_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct ad5272_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW: {
+ ret = ad5272_read(data, AD5272_RDAC_RD, val);
+ *val = *val >> data->cfg->shift;
+ return ret ? ret : IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ *val = 1000 * data->cfg->kohms;
+ *val2 = data->cfg->max_pos;
+ return IIO_VAL_FRACTIONAL;
+ }
+
+ return -EINVAL;
+}
+
+static int ad5272_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct ad5272_data *data = iio_priv(indio_dev);
+
+ if (mask != IIO_CHAN_INFO_RAW)
+ return -EINVAL;
+
+ if (val >= data->cfg->max_pos || val < 0 || val2)
+ return -EINVAL;
+
+ return ad5272_write(data, AD5272_RDAC_WR, val << data->cfg->shift);
+}
+
+static const struct iio_info ad5272_info = {
+ .read_raw = ad5272_read_raw,
+ .write_raw = ad5272_write_raw,
+};
+
+static int ad5272_reset(struct ad5272_data *data)
+{
+ struct gpio_desc *reset_gpio;
+
+ reset_gpio = devm_gpiod_get_optional(&data->client->dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(reset_gpio))
+ return PTR_ERR(reset_gpio);
+
+ if (reset_gpio) {
+ udelay(1);
+ gpiod_set_value(reset_gpio, 0);
+ } else {
+ ad5272_write(data, AD5272_RESET, 0);
+ }
+ usleep_range(1000, 2000);
+
+ return 0;
+}
+
+static int ad5272_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct device *dev = &client->dev;
+ struct iio_dev *indio_dev;
+ struct ad5272_data *data;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, indio_dev);
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ mutex_init(&data->lock);
+ data->cfg = &ad5272_cfg[id->driver_data];
+
+ ret = ad5272_reset(data);
+ if (ret)
+ return ret;
+
+ ret = ad5272_write(data, AD5272_CTL, AD5272_RDAC_WR_EN);
+ if (ret < 0)
+ return -ENODEV;
+
+ indio_dev->info = &ad5272_info;
+ indio_dev->channels = &ad5272_channel;
+ indio_dev->num_channels = 1;
+ indio_dev->name = client->name;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id ad5272_dt_ids[] = {
+ { .compatible = "adi,ad5272-020", .data = (void *)AD5272_020 },
+ { .compatible = "adi,ad5272-050", .data = (void *)AD5272_050 },
+ { .compatible = "adi,ad5272-100", .data = (void *)AD5272_100 },
+ { .compatible = "adi,ad5274-020", .data = (void *)AD5274_020 },
+ { .compatible = "adi,ad5274-100", .data = (void *)AD5274_100 },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ad5272_dt_ids);
+
+static const struct i2c_device_id ad5272_id[] = {
+ { "ad5272-020", AD5272_020 },
+ { "ad5272-050", AD5272_050 },
+ { "ad5272-100", AD5272_100 },
+ { "ad5274-020", AD5274_020 },
+ { "ad5274-100", AD5274_100 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ad5272_id);
+
+static struct i2c_driver ad5272_driver = {
+ .driver = {
+ .name = "ad5272",
+ .of_match_table = ad5272_dt_ids,
+ },
+ .probe = ad5272_probe,
+ .id_table = ad5272_id,
+};
+
+module_i2c_driver(ad5272_driver);
+
+MODULE_AUTHOR("Phil Reid <preid@eletromag.com.au>");
+MODULE_DESCRIPTION("AD5272 digital potentiometer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/potentiometer/ds1803.c b/drivers/iio/potentiometer/ds1803.c
new file mode 100644
index 0000000000..fc183e0790
--- /dev/null
+++ b/drivers/iio/potentiometer/ds1803.c
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Maxim Integrated DS1803 and similar digital potentiometer driver
+ * Copyright (c) 2016 Slawomir Stepien
+ * Copyright (c) 2022 Jagath Jog J
+ *
+ * Datasheet: https://datasheets.maximintegrated.com/en/ds/DS1803.pdf
+ * Datasheet: https://datasheets.maximintegrated.com/en/ds/DS3502.pdf
+ *
+ * DEVID #Wipers #Positions Resistor Opts (kOhm) i2c address
+ * ds1803 2 256 10, 50, 100 0101xxx
+ * ds3502 1 128 10 01010xx
+ */
+
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+
+#define DS1803_WIPER_0 0xA9
+#define DS1803_WIPER_1 0xAA
+#define DS3502_WR_IVR 0x00
+
+enum ds1803_type {
+ DS1803_010,
+ DS1803_050,
+ DS1803_100,
+ DS3502,
+};
+
+struct ds1803_cfg {
+ int wipers;
+ int avail[3];
+ int kohms;
+ const struct iio_chan_spec *channels;
+ u8 num_channels;
+ int (*read)(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val);
+};
+
+struct ds1803_data {
+ struct i2c_client *client;
+ const struct ds1803_cfg *cfg;
+};
+
+#define DS1803_CHANNEL(ch, addr) { \
+ .type = IIO_RESISTANCE, \
+ .indexed = 1, \
+ .output = 1, \
+ .channel = (ch), \
+ .address = (addr), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_RAW), \
+}
+
+static const struct iio_chan_spec ds1803_channels[] = {
+ DS1803_CHANNEL(0, DS1803_WIPER_0),
+ DS1803_CHANNEL(1, DS1803_WIPER_1),
+};
+
+static const struct iio_chan_spec ds3502_channels[] = {
+ DS1803_CHANNEL(0, DS3502_WR_IVR),
+};
+
+static int ds1803_read(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val)
+{
+ struct ds1803_data *data = iio_priv(indio_dev);
+ int ret;
+ u8 result[ARRAY_SIZE(ds1803_channels)];
+
+ ret = i2c_master_recv(data->client, result, indio_dev->num_channels);
+ if (ret < 0)
+ return ret;
+
+ *val = result[chan->channel];
+ return ret;
+}
+
+static int ds3502_read(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val)
+{
+ struct ds1803_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(data->client, chan->address);
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+ return ret;
+}
+
+static const struct ds1803_cfg ds1803_cfg[] = {
+ [DS1803_010] = {
+ .wipers = 2,
+ .avail = { 0, 1, 255 },
+ .kohms = 10,
+ .channels = ds1803_channels,
+ .num_channels = ARRAY_SIZE(ds1803_channels),
+ .read = ds1803_read,
+ },
+ [DS1803_050] = {
+ .wipers = 2,
+ .avail = { 0, 1, 255 },
+ .kohms = 50,
+ .channels = ds1803_channels,
+ .num_channels = ARRAY_SIZE(ds1803_channels),
+ .read = ds1803_read,
+ },
+ [DS1803_100] = {
+ .wipers = 2,
+ .avail = { 0, 1, 255 },
+ .kohms = 100,
+ .channels = ds1803_channels,
+ .num_channels = ARRAY_SIZE(ds1803_channels),
+ .read = ds1803_read,
+ },
+ [DS3502] = {
+ .wipers = 1,
+ .avail = { 0, 1, 127 },
+ .kohms = 10,
+ .channels = ds3502_channels,
+ .num_channels = ARRAY_SIZE(ds3502_channels),
+ .read = ds3502_read,
+ },
+};
+
+static int ds1803_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct ds1803_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = data->cfg->read(indio_dev, chan, val);
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = 1000 * data->cfg->kohms;
+ *val2 = data->cfg->avail[2]; /* Max wiper position */
+ return IIO_VAL_FRACTIONAL;
+ }
+
+ return -EINVAL;
+}
+
+static int ds1803_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct ds1803_data *data = iio_priv(indio_dev);
+ u8 addr = chan->address;
+ int max_pos = data->cfg->avail[2];
+
+ if (val2 != 0)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (val > max_pos || val < 0)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return i2c_smbus_write_byte_data(data->client, addr, val);
+}
+
+static int ds1803_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type,
+ int *length, long mask)
+{
+ struct ds1803_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ *vals = data->cfg->avail;
+ *length = ARRAY_SIZE(data->cfg->avail);
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_RANGE;
+ }
+ return -EINVAL;
+}
+
+static const struct iio_info ds1803_info = {
+ .read_raw = ds1803_read_raw,
+ .write_raw = ds1803_write_raw,
+ .read_avail = ds1803_read_avail,
+};
+
+static int ds1803_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct device *dev = &client->dev;
+ struct ds1803_data *data;
+ struct iio_dev *indio_dev;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, indio_dev);
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ data->cfg = device_get_match_data(dev);
+ if (!data->cfg)
+ data->cfg = &ds1803_cfg[id->driver_data];
+
+ indio_dev->info = &ds1803_info;
+ indio_dev->channels = data->cfg->channels;
+ indio_dev->num_channels = data->cfg->num_channels;
+ indio_dev->name = client->name;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id ds1803_dt_ids[] = {
+ { .compatible = "maxim,ds1803-010", .data = &ds1803_cfg[DS1803_010] },
+ { .compatible = "maxim,ds1803-050", .data = &ds1803_cfg[DS1803_050] },
+ { .compatible = "maxim,ds1803-100", .data = &ds1803_cfg[DS1803_100] },
+ { .compatible = "maxim,ds3502", .data = &ds1803_cfg[DS3502] },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ds1803_dt_ids);
+
+static const struct i2c_device_id ds1803_id[] = {
+ { "ds1803-010", DS1803_010 },
+ { "ds1803-050", DS1803_050 },
+ { "ds1803-100", DS1803_100 },
+ { "ds3502", DS3502 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ds1803_id);
+
+static struct i2c_driver ds1803_driver = {
+ .driver = {
+ .name = "ds1803",
+ .of_match_table = ds1803_dt_ids,
+ },
+ .probe = ds1803_probe,
+ .id_table = ds1803_id,
+};
+
+module_i2c_driver(ds1803_driver);
+
+MODULE_AUTHOR("Slawomir Stepien <sst@poczta.fm>");
+MODULE_AUTHOR("Jagath Jog J <jagathjog1996@gmail.com>");
+MODULE_DESCRIPTION("DS1803 digital potentiometer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/potentiometer/max5432.c b/drivers/iio/potentiometer/max5432.c
new file mode 100644
index 0000000000..c8e2481dad
--- /dev/null
+++ b/drivers/iio/potentiometer/max5432.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Maxim Integrated MAX5432-MAX5435 digital potentiometer driver
+ * Copyright (C) 2019 Martin Kaiser <martin@kaiser.cx>
+ *
+ * Datasheet:
+ * https://datasheets.maximintegrated.com/en/ds/MAX5432-MAX5435.pdf
+ */
+
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+
+/* All chip variants have 32 wiper positions. */
+#define MAX5432_MAX_POS 31
+
+#define MAX5432_OHM_50K (50 * 1000)
+#define MAX5432_OHM_100K (100 * 1000)
+
+/* Update the volatile (currently active) setting. */
+#define MAX5432_CMD_VREG 0x11
+
+struct max5432_data {
+ struct i2c_client *client;
+ unsigned long ohm;
+};
+
+static const struct iio_chan_spec max5432_channels[] = {
+ {
+ .type = IIO_RESISTANCE,
+ .indexed = 1,
+ .output = 1,
+ .channel = 0,
+ .address = MAX5432_CMD_VREG,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+ }
+};
+
+static int max5432_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct max5432_data *data = iio_priv(indio_dev);
+
+ if (mask != IIO_CHAN_INFO_SCALE)
+ return -EINVAL;
+
+ if (unlikely(data->ohm > INT_MAX))
+ return -ERANGE;
+
+ *val = data->ohm;
+ *val2 = MAX5432_MAX_POS;
+
+ return IIO_VAL_FRACTIONAL;
+}
+
+static int max5432_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct max5432_data *data = iio_priv(indio_dev);
+ u8 data_byte;
+
+ if (mask != IIO_CHAN_INFO_RAW)
+ return -EINVAL;
+
+ if (val < 0 || val > MAX5432_MAX_POS)
+ return -EINVAL;
+
+ if (val2 != 0)
+ return -EINVAL;
+
+ /* Wiper position is in bits D7-D3. (D2-D0 are don't care bits.) */
+ data_byte = val << 3;
+ return i2c_smbus_write_byte_data(data->client, chan->address,
+ data_byte);
+}
+
+static const struct iio_info max5432_info = {
+ .read_raw = max5432_read_raw,
+ .write_raw = max5432_write_raw,
+};
+
+static int max5432_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct iio_dev *indio_dev;
+ struct max5432_data *data;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(struct max5432_data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, indio_dev);
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ data->ohm = (unsigned long)device_get_match_data(dev);
+
+ indio_dev->info = &max5432_info;
+ indio_dev->channels = max5432_channels;
+ indio_dev->num_channels = ARRAY_SIZE(max5432_channels);
+ indio_dev->name = client->name;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id max5432_dt_ids[] = {
+ { .compatible = "maxim,max5432", .data = (void *)MAX5432_OHM_50K },
+ { .compatible = "maxim,max5433", .data = (void *)MAX5432_OHM_100K },
+ { .compatible = "maxim,max5434", .data = (void *)MAX5432_OHM_50K },
+ { .compatible = "maxim,max5435", .data = (void *)MAX5432_OHM_100K },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, max5432_dt_ids);
+
+static struct i2c_driver max5432_driver = {
+ .driver = {
+ .name = "max5432",
+ .of_match_table = max5432_dt_ids,
+ },
+ .probe = max5432_probe,
+};
+
+module_i2c_driver(max5432_driver);
+
+MODULE_AUTHOR("Martin Kaiser <martin@kaiser.cx>");
+MODULE_DESCRIPTION("max5432-max5435 digital potentiometers");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/potentiometer/max5481.c b/drivers/iio/potentiometer/max5481.c
new file mode 100644
index 0000000000..b40e5ac218
--- /dev/null
+++ b/drivers/iio/potentiometer/max5481.c
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Maxim Integrated MAX5481-MAX5484 digital potentiometer driver
+ * Copyright 2016 Rockwell Collins
+ *
+ * Datasheet:
+ * https://datasheets.maximintegrated.com/en/ds/MAX5481-MAX5484.pdf
+ */
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/spi/spi.h>
+
+/* write wiper reg */
+#define MAX5481_WRITE_WIPER (0 << 4)
+/* copy wiper reg to NV reg */
+#define MAX5481_COPY_AB_TO_NV (2 << 4)
+/* copy NV reg to wiper reg */
+#define MAX5481_COPY_NV_TO_AB (3 << 4)
+
+#define MAX5481_MAX_POS 1023
+
+enum max5481_variant {
+ max5481,
+ max5482,
+ max5483,
+ max5484,
+};
+
+struct max5481_cfg {
+ int kohms;
+};
+
+static const struct max5481_cfg max5481_cfg[] = {
+ [max5481] = { .kohms = 10, },
+ [max5482] = { .kohms = 50, },
+ [max5483] = { .kohms = 10, },
+ [max5484] = { .kohms = 50, },
+};
+
+struct max5481_data {
+ struct spi_device *spi;
+ const struct max5481_cfg *cfg;
+ u8 msg[3] __aligned(IIO_DMA_MINALIGN);
+};
+
+#define MAX5481_CHANNEL { \
+ .type = IIO_RESISTANCE, \
+ .indexed = 1, \
+ .output = 1, \
+ .channel = 0, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+}
+
+static const struct iio_chan_spec max5481_channels[] = {
+ MAX5481_CHANNEL,
+};
+
+static int max5481_write_cmd(struct max5481_data *data, u8 cmd, u16 val)
+{
+ struct spi_device *spi = data->spi;
+
+ data->msg[0] = cmd;
+
+ switch (cmd) {
+ case MAX5481_WRITE_WIPER:
+ data->msg[1] = val >> 2;
+ data->msg[2] = (val & 0x3) << 6;
+ return spi_write(spi, data->msg, 3);
+
+ case MAX5481_COPY_AB_TO_NV:
+ case MAX5481_COPY_NV_TO_AB:
+ return spi_write(spi, data->msg, 1);
+
+ default:
+ return -EIO;
+ }
+}
+
+static int max5481_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct max5481_data *data = iio_priv(indio_dev);
+
+ if (mask != IIO_CHAN_INFO_SCALE)
+ return -EINVAL;
+
+ *val = 1000 * data->cfg->kohms;
+ *val2 = MAX5481_MAX_POS;
+
+ return IIO_VAL_FRACTIONAL;
+}
+
+static int max5481_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct max5481_data *data = iio_priv(indio_dev);
+
+ if (mask != IIO_CHAN_INFO_RAW)
+ return -EINVAL;
+
+ if (val < 0 || val > MAX5481_MAX_POS)
+ return -EINVAL;
+
+ return max5481_write_cmd(data, MAX5481_WRITE_WIPER, val);
+}
+
+static const struct iio_info max5481_info = {
+ .read_raw = max5481_read_raw,
+ .write_raw = max5481_write_raw,
+};
+
+static const struct of_device_id max5481_match[] = {
+ { .compatible = "maxim,max5481", .data = &max5481_cfg[max5481] },
+ { .compatible = "maxim,max5482", .data = &max5481_cfg[max5482] },
+ { .compatible = "maxim,max5483", .data = &max5481_cfg[max5483] },
+ { .compatible = "maxim,max5484", .data = &max5481_cfg[max5484] },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max5481_match);
+
+static void max5481_wiper_save(void *data)
+{
+ max5481_write_cmd(data, MAX5481_COPY_AB_TO_NV, 0);
+}
+
+static int max5481_probe(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev;
+ struct max5481_data *data;
+ const struct spi_device_id *id = spi_get_device_id(spi);
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+
+ data->spi = spi;
+
+ data->cfg = device_get_match_data(&spi->dev);
+ if (!data->cfg)
+ data->cfg = &max5481_cfg[id->driver_data];
+
+ indio_dev->name = id->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ /* variant specific configuration */
+ indio_dev->info = &max5481_info;
+ indio_dev->channels = max5481_channels;
+ indio_dev->num_channels = ARRAY_SIZE(max5481_channels);
+
+ /* restore wiper from NV */
+ ret = max5481_write_cmd(data, MAX5481_COPY_NV_TO_AB, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_add_action(&spi->dev, max5481_wiper_save, data);
+ if (ret < 0)
+ return ret;
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct spi_device_id max5481_id_table[] = {
+ { "max5481", max5481 },
+ { "max5482", max5482 },
+ { "max5483", max5483 },
+ { "max5484", max5484 },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, max5481_id_table);
+
+static struct spi_driver max5481_driver = {
+ .driver = {
+ .name = "max5481",
+ .of_match_table = max5481_match,
+ },
+ .probe = max5481_probe,
+ .id_table = max5481_id_table,
+};
+
+module_spi_driver(max5481_driver);
+
+MODULE_AUTHOR("Maury Anderson <maury.anderson@rockwellcollins.com>");
+MODULE_DESCRIPTION("max5481 SPI driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/potentiometer/max5487.c b/drivers/iio/potentiometer/max5487.c
new file mode 100644
index 0000000000..42723c996c
--- /dev/null
+++ b/drivers/iio/potentiometer/max5487.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * max5487.c - Support for MAX5487, MAX5488, MAX5489 digital potentiometers
+ *
+ * Copyright (C) 2016 Cristina-Gabriela Moraru <cristina.moraru09@gmail.com>
+ */
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/acpi.h>
+
+#include <linux/iio/sysfs.h>
+#include <linux/iio/iio.h>
+
+#define MAX5487_WRITE_WIPER_A (0x01 << 8)
+#define MAX5487_WRITE_WIPER_B (0x02 << 8)
+
+/* copy both wiper regs to NV regs */
+#define MAX5487_COPY_AB_TO_NV (0x23 << 8)
+/* copy both NV regs to wiper regs */
+#define MAX5487_COPY_NV_TO_AB (0x33 << 8)
+
+#define MAX5487_MAX_POS 255
+
+struct max5487_data {
+ struct spi_device *spi;
+ int kohms;
+};
+
+#define MAX5487_CHANNEL(ch, addr) { \
+ .type = IIO_RESISTANCE, \
+ .indexed = 1, \
+ .output = 1, \
+ .channel = ch, \
+ .address = addr, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+}
+
+static const struct iio_chan_spec max5487_channels[] = {
+ MAX5487_CHANNEL(0, MAX5487_WRITE_WIPER_A),
+ MAX5487_CHANNEL(1, MAX5487_WRITE_WIPER_B),
+};
+
+static int max5487_write_cmd(struct spi_device *spi, u16 cmd)
+{
+ return spi_write(spi, (const void *) &cmd, sizeof(u16));
+}
+
+static int max5487_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct max5487_data *data = iio_priv(indio_dev);
+
+ if (mask != IIO_CHAN_INFO_SCALE)
+ return -EINVAL;
+
+ *val = 1000 * data->kohms;
+ *val2 = MAX5487_MAX_POS;
+
+ return IIO_VAL_FRACTIONAL;
+}
+
+static int max5487_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct max5487_data *data = iio_priv(indio_dev);
+
+ if (mask != IIO_CHAN_INFO_RAW)
+ return -EINVAL;
+
+ if (val < 0 || val > MAX5487_MAX_POS)
+ return -EINVAL;
+
+ return max5487_write_cmd(data->spi, chan->address | val);
+}
+
+static const struct iio_info max5487_info = {
+ .read_raw = max5487_read_raw,
+ .write_raw = max5487_write_raw,
+};
+
+static int max5487_spi_probe(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev;
+ struct max5487_data *data;
+ const struct spi_device_id *id = spi_get_device_id(spi);
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, indio_dev);
+ data = iio_priv(indio_dev);
+
+ data->spi = spi;
+ data->kohms = id->driver_data;
+
+ indio_dev->info = &max5487_info;
+ indio_dev->name = id->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = max5487_channels;
+ indio_dev->num_channels = ARRAY_SIZE(max5487_channels);
+
+ /* restore both wiper regs from NV regs */
+ ret = max5487_write_cmd(data->spi, MAX5487_COPY_NV_TO_AB);
+ if (ret < 0)
+ return ret;
+
+ return iio_device_register(indio_dev);
+}
+
+static void max5487_spi_remove(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev = spi_get_drvdata(spi);
+ int ret;
+
+ iio_device_unregister(indio_dev);
+
+ /* save both wiper regs to NV regs */
+ ret = max5487_write_cmd(spi, MAX5487_COPY_AB_TO_NV);
+ if (ret)
+ dev_warn(&spi->dev, "Failed to save wiper regs to NV regs\n");
+}
+
+static const struct spi_device_id max5487_id[] = {
+ { "MAX5487", 10 },
+ { "MAX5488", 50 },
+ { "MAX5489", 100 },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, max5487_id);
+
+static const struct acpi_device_id max5487_acpi_match[] = {
+ { "MAX5487", 10 },
+ { "MAX5488", 50 },
+ { "MAX5489", 100 },
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, max5487_acpi_match);
+
+static struct spi_driver max5487_driver = {
+ .driver = {
+ .name = "max5487",
+ .acpi_match_table = ACPI_PTR(max5487_acpi_match),
+ },
+ .id_table = max5487_id,
+ .probe = max5487_spi_probe,
+ .remove = max5487_spi_remove
+};
+module_spi_driver(max5487_driver);
+
+MODULE_AUTHOR("Cristina-Gabriela Moraru <cristina.moraru09@gmail.com>");
+MODULE_DESCRIPTION("max5487 SPI driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/potentiometer/mcp4018.c b/drivers/iio/potentiometer/mcp4018.c
new file mode 100644
index 0000000000..44678d3721
--- /dev/null
+++ b/drivers/iio/potentiometer/mcp4018.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Industrial I/O driver for Microchip digital potentiometers
+ * Copyright (c) 2018 Axentia Technologies AB
+ * Author: Peter Rosin <peda@axentia.se>
+ *
+ * Datasheet: http://www.microchip.com/downloads/en/DeviceDoc/22147a.pdf
+ *
+ * DEVID #Wipers #Positions Resistor Opts (kOhm)
+ * mcp4017 1 128 5, 10, 50, 100
+ * mcp4018 1 128 5, 10, 50, 100
+ * mcp4019 1 128 5, 10, 50, 100
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+
+#define MCP4018_WIPER_MAX 127
+
+struct mcp4018_cfg {
+ int kohms;
+};
+
+enum mcp4018_type {
+ MCP4018_502,
+ MCP4018_103,
+ MCP4018_503,
+ MCP4018_104,
+};
+
+static const struct mcp4018_cfg mcp4018_cfg[] = {
+ [MCP4018_502] = { .kohms = 5, },
+ [MCP4018_103] = { .kohms = 10, },
+ [MCP4018_503] = { .kohms = 50, },
+ [MCP4018_104] = { .kohms = 100, },
+};
+
+struct mcp4018_data {
+ struct i2c_client *client;
+ const struct mcp4018_cfg *cfg;
+};
+
+static const struct iio_chan_spec mcp4018_channel = {
+ .type = IIO_RESISTANCE,
+ .indexed = 1,
+ .output = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+};
+
+static int mcp4018_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mcp4018_data *data = iio_priv(indio_dev);
+ s32 ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = i2c_smbus_read_byte(data->client);
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 1000 * data->cfg->kohms;
+ *val2 = MCP4018_WIPER_MAX;
+ return IIO_VAL_FRACTIONAL;
+ }
+
+ return -EINVAL;
+}
+
+static int mcp4018_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct mcp4018_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (val > MCP4018_WIPER_MAX || val < 0)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return i2c_smbus_write_byte(data->client, val);
+}
+
+static const struct iio_info mcp4018_info = {
+ .read_raw = mcp4018_read_raw,
+ .write_raw = mcp4018_write_raw,
+};
+
+#define MCP4018_ID_TABLE(_name, cfg) { \
+ .name = _name, \
+ .driver_data = (kernel_ulong_t)&mcp4018_cfg[cfg], \
+}
+
+static const struct i2c_device_id mcp4018_id[] = {
+ MCP4018_ID_TABLE("mcp4017-502", MCP4018_502),
+ MCP4018_ID_TABLE("mcp4017-103", MCP4018_103),
+ MCP4018_ID_TABLE("mcp4017-503", MCP4018_503),
+ MCP4018_ID_TABLE("mcp4017-104", MCP4018_104),
+ MCP4018_ID_TABLE("mcp4018-502", MCP4018_502),
+ MCP4018_ID_TABLE("mcp4018-103", MCP4018_103),
+ MCP4018_ID_TABLE("mcp4018-503", MCP4018_503),
+ MCP4018_ID_TABLE("mcp4018-104", MCP4018_104),
+ MCP4018_ID_TABLE("mcp4019-502", MCP4018_502),
+ MCP4018_ID_TABLE("mcp4019-103", MCP4018_103),
+ MCP4018_ID_TABLE("mcp4019-503", MCP4018_503),
+ MCP4018_ID_TABLE("mcp4019-104", MCP4018_104),
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, mcp4018_id);
+
+#define MCP4018_COMPATIBLE(of_compatible, cfg) { \
+ .compatible = of_compatible, \
+ .data = &mcp4018_cfg[cfg], \
+}
+
+static const struct of_device_id mcp4018_of_match[] = {
+ MCP4018_COMPATIBLE("microchip,mcp4017-502", MCP4018_502),
+ MCP4018_COMPATIBLE("microchip,mcp4017-103", MCP4018_103),
+ MCP4018_COMPATIBLE("microchip,mcp4017-503", MCP4018_503),
+ MCP4018_COMPATIBLE("microchip,mcp4017-104", MCP4018_104),
+ MCP4018_COMPATIBLE("microchip,mcp4018-502", MCP4018_502),
+ MCP4018_COMPATIBLE("microchip,mcp4018-103", MCP4018_103),
+ MCP4018_COMPATIBLE("microchip,mcp4018-503", MCP4018_503),
+ MCP4018_COMPATIBLE("microchip,mcp4018-104", MCP4018_104),
+ MCP4018_COMPATIBLE("microchip,mcp4019-502", MCP4018_502),
+ MCP4018_COMPATIBLE("microchip,mcp4019-103", MCP4018_103),
+ MCP4018_COMPATIBLE("microchip,mcp4019-503", MCP4018_503),
+ MCP4018_COMPATIBLE("microchip,mcp4019-104", MCP4018_104),
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mcp4018_of_match);
+
+static int mcp4018_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct mcp4018_data *data;
+ struct iio_dev *indio_dev;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE)) {
+ dev_err(dev, "SMBUS Byte transfers not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+
+ data->cfg = i2c_get_match_data(client);
+
+ indio_dev->info = &mcp4018_info;
+ indio_dev->channels = &mcp4018_channel;
+ indio_dev->num_channels = 1;
+ indio_dev->name = client->name;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static struct i2c_driver mcp4018_driver = {
+ .driver = {
+ .name = "mcp4018",
+ .of_match_table = mcp4018_of_match,
+ },
+ .probe = mcp4018_probe,
+ .id_table = mcp4018_id,
+};
+
+module_i2c_driver(mcp4018_driver);
+
+MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
+MODULE_DESCRIPTION("MCP4018 digital potentiometer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/potentiometer/mcp41010.c b/drivers/iio/potentiometer/mcp41010.c
new file mode 100644
index 0000000000..2b73c75402
--- /dev/null
+++ b/drivers/iio/potentiometer/mcp41010.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Industrial I/O driver for Microchip digital potentiometers
+ *
+ * Copyright (c) 2018 Chris Coffey <cmc@babblebit.net>
+ * Based on: Slawomir Stepien's code from mcp4131.c
+ *
+ * Datasheet: https://ww1.microchip.com/downloads/en/devicedoc/11195c.pdf
+ *
+ * DEVID #Wipers #Positions Resistance (kOhm)
+ * mcp41010 1 256 10
+ * mcp41050 1 256 50
+ * mcp41100 1 256 100
+ * mcp42010 2 256 10
+ * mcp42050 2 256 50
+ * mcp42100 2 256 100
+ */
+
+#include <linux/cache.h>
+#include <linux/err.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/types.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/spi/spi.h>
+
+#define MCP41010_MAX_WIPERS 2
+#define MCP41010_WRITE BIT(4)
+#define MCP41010_WIPER_MAX 255
+#define MCP41010_WIPER_CHANNEL BIT(0)
+
+struct mcp41010_cfg {
+ char name[16];
+ int wipers;
+ int kohms;
+};
+
+enum mcp41010_type {
+ MCP41010,
+ MCP41050,
+ MCP41100,
+ MCP42010,
+ MCP42050,
+ MCP42100,
+};
+
+static const struct mcp41010_cfg mcp41010_cfg[] = {
+ [MCP41010] = { .name = "mcp41010", .wipers = 1, .kohms = 10, },
+ [MCP41050] = { .name = "mcp41050", .wipers = 1, .kohms = 50, },
+ [MCP41100] = { .name = "mcp41100", .wipers = 1, .kohms = 100, },
+ [MCP42010] = { .name = "mcp42010", .wipers = 2, .kohms = 10, },
+ [MCP42050] = { .name = "mcp42050", .wipers = 2, .kohms = 50, },
+ [MCP42100] = { .name = "mcp42100", .wipers = 2, .kohms = 100, },
+};
+
+struct mcp41010_data {
+ struct spi_device *spi;
+ const struct mcp41010_cfg *cfg;
+ struct mutex lock; /* Protect write sequences */
+ unsigned int value[MCP41010_MAX_WIPERS]; /* Cache wiper values */
+ u8 buf[2] __aligned(IIO_DMA_MINALIGN);
+};
+
+#define MCP41010_CHANNEL(ch) { \
+ .type = IIO_RESISTANCE, \
+ .indexed = 1, \
+ .output = 1, \
+ .channel = (ch), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+}
+
+static const struct iio_chan_spec mcp41010_channels[] = {
+ MCP41010_CHANNEL(0),
+ MCP41010_CHANNEL(1),
+};
+
+static int mcp41010_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mcp41010_data *data = iio_priv(indio_dev);
+ int channel = chan->channel;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ *val = data->value[channel];
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = 1000 * data->cfg->kohms;
+ *val2 = MCP41010_WIPER_MAX;
+ return IIO_VAL_FRACTIONAL;
+ }
+
+ return -EINVAL;
+}
+
+static int mcp41010_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ int err;
+ struct mcp41010_data *data = iio_priv(indio_dev);
+ int channel = chan->channel;
+
+ if (mask != IIO_CHAN_INFO_RAW)
+ return -EINVAL;
+
+ if (val > MCP41010_WIPER_MAX || val < 0)
+ return -EINVAL;
+
+ mutex_lock(&data->lock);
+
+ data->buf[0] = MCP41010_WIPER_CHANNEL << channel;
+ data->buf[0] |= MCP41010_WRITE;
+ data->buf[1] = val & 0xff;
+
+ err = spi_write(data->spi, data->buf, sizeof(data->buf));
+ if (!err)
+ data->value[channel] = val;
+
+ mutex_unlock(&data->lock);
+
+ return err;
+}
+
+static const struct iio_info mcp41010_info = {
+ .read_raw = mcp41010_read_raw,
+ .write_raw = mcp41010_write_raw,
+};
+
+static int mcp41010_probe(struct spi_device *spi)
+{
+ int err;
+ struct device *dev = &spi->dev;
+ struct mcp41010_data *data;
+ struct iio_dev *indio_dev;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ spi_set_drvdata(spi, indio_dev);
+ data->spi = spi;
+ data->cfg = device_get_match_data(&spi->dev);
+ if (!data->cfg)
+ data->cfg = &mcp41010_cfg[spi_get_device_id(spi)->driver_data];
+
+ mutex_init(&data->lock);
+
+ indio_dev->info = &mcp41010_info;
+ indio_dev->channels = mcp41010_channels;
+ indio_dev->num_channels = data->cfg->wipers;
+ indio_dev->name = data->cfg->name;
+
+ err = devm_iio_device_register(dev, indio_dev);
+ if (err)
+ dev_info(&spi->dev, "Unable to register %s\n", indio_dev->name);
+
+ return err;
+}
+
+static const struct of_device_id mcp41010_match[] = {
+ { .compatible = "microchip,mcp41010", .data = &mcp41010_cfg[MCP41010] },
+ { .compatible = "microchip,mcp41050", .data = &mcp41010_cfg[MCP41050] },
+ { .compatible = "microchip,mcp41100", .data = &mcp41010_cfg[MCP41100] },
+ { .compatible = "microchip,mcp42010", .data = &mcp41010_cfg[MCP42010] },
+ { .compatible = "microchip,mcp42050", .data = &mcp41010_cfg[MCP42050] },
+ { .compatible = "microchip,mcp42100", .data = &mcp41010_cfg[MCP42100] },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mcp41010_match);
+
+static const struct spi_device_id mcp41010_id[] = {
+ { "mcp41010", MCP41010 },
+ { "mcp41050", MCP41050 },
+ { "mcp41100", MCP41100 },
+ { "mcp42010", MCP42010 },
+ { "mcp42050", MCP42050 },
+ { "mcp42100", MCP42100 },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, mcp41010_id);
+
+static struct spi_driver mcp41010_driver = {
+ .driver = {
+ .name = "mcp41010",
+ .of_match_table = mcp41010_match,
+ },
+ .probe = mcp41010_probe,
+ .id_table = mcp41010_id,
+};
+
+module_spi_driver(mcp41010_driver);
+
+MODULE_AUTHOR("Chris Coffey <cmc@babblebit.net>");
+MODULE_DESCRIPTION("MCP41010 digital potentiometer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/potentiometer/mcp4131.c b/drivers/iio/potentiometer/mcp4131.c
new file mode 100644
index 0000000000..7890c0993e
--- /dev/null
+++ b/drivers/iio/potentiometer/mcp4131.c
@@ -0,0 +1,492 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Industrial I/O driver for Microchip digital potentiometers
+ *
+ * Copyright (c) 2016 Slawomir Stepien
+ * Based on: Peter Rosin's code from mcp4531.c
+ *
+ * Datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/22060b.pdf
+ *
+ * DEVID #Wipers #Positions Resistor Opts (kOhm)
+ * mcp4131 1 129 5, 10, 50, 100
+ * mcp4132 1 129 5, 10, 50, 100
+ * mcp4141 1 129 5, 10, 50, 100
+ * mcp4142 1 129 5, 10, 50, 100
+ * mcp4151 1 257 5, 10, 50, 100
+ * mcp4152 1 257 5, 10, 50, 100
+ * mcp4161 1 257 5, 10, 50, 100
+ * mcp4162 1 257 5, 10, 50, 100
+ * mcp4231 2 129 5, 10, 50, 100
+ * mcp4232 2 129 5, 10, 50, 100
+ * mcp4241 2 129 5, 10, 50, 100
+ * mcp4242 2 129 5, 10, 50, 100
+ * mcp4251 2 257 5, 10, 50, 100
+ * mcp4252 2 257 5, 10, 50, 100
+ * mcp4261 2 257 5, 10, 50, 100
+ * mcp4262 2 257 5, 10, 50, 100
+ */
+
+/*
+ * TODO:
+ * 1. Write wiper setting to EEPROM for EEPROM capable models.
+ */
+
+#include <linux/cache.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/types.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/spi/spi.h>
+
+#define MCP4131_WRITE (0x00 << 2)
+#define MCP4131_READ (0x03 << 2)
+
+#define MCP4131_WIPER_SHIFT 4
+#define MCP4131_CMDERR(r) ((r[0]) & 0x02)
+#define MCP4131_RAW(r) ((r[0]) == 0xff ? 0x100 : (r[1]))
+
+struct mcp4131_cfg {
+ int wipers;
+ int max_pos;
+ int kohms;
+};
+
+enum mcp4131_type {
+ MCP413x_502 = 0,
+ MCP413x_103,
+ MCP413x_503,
+ MCP413x_104,
+ MCP414x_502,
+ MCP414x_103,
+ MCP414x_503,
+ MCP414x_104,
+ MCP415x_502,
+ MCP415x_103,
+ MCP415x_503,
+ MCP415x_104,
+ MCP416x_502,
+ MCP416x_103,
+ MCP416x_503,
+ MCP416x_104,
+ MCP423x_502,
+ MCP423x_103,
+ MCP423x_503,
+ MCP423x_104,
+ MCP424x_502,
+ MCP424x_103,
+ MCP424x_503,
+ MCP424x_104,
+ MCP425x_502,
+ MCP425x_103,
+ MCP425x_503,
+ MCP425x_104,
+ MCP426x_502,
+ MCP426x_103,
+ MCP426x_503,
+ MCP426x_104,
+};
+
+static const struct mcp4131_cfg mcp4131_cfg[] = {
+ [MCP413x_502] = { .wipers = 1, .max_pos = 128, .kohms = 5, },
+ [MCP413x_103] = { .wipers = 1, .max_pos = 128, .kohms = 10, },
+ [MCP413x_503] = { .wipers = 1, .max_pos = 128, .kohms = 50, },
+ [MCP413x_104] = { .wipers = 1, .max_pos = 128, .kohms = 100, },
+ [MCP414x_502] = { .wipers = 1, .max_pos = 128, .kohms = 5, },
+ [MCP414x_103] = { .wipers = 1, .max_pos = 128, .kohms = 10, },
+ [MCP414x_503] = { .wipers = 1, .max_pos = 128, .kohms = 50, },
+ [MCP414x_104] = { .wipers = 1, .max_pos = 128, .kohms = 100, },
+ [MCP415x_502] = { .wipers = 1, .max_pos = 256, .kohms = 5, },
+ [MCP415x_103] = { .wipers = 1, .max_pos = 256, .kohms = 10, },
+ [MCP415x_503] = { .wipers = 1, .max_pos = 256, .kohms = 50, },
+ [MCP415x_104] = { .wipers = 1, .max_pos = 256, .kohms = 100, },
+ [MCP416x_502] = { .wipers = 1, .max_pos = 256, .kohms = 5, },
+ [MCP416x_103] = { .wipers = 1, .max_pos = 256, .kohms = 10, },
+ [MCP416x_503] = { .wipers = 1, .max_pos = 256, .kohms = 50, },
+ [MCP416x_104] = { .wipers = 1, .max_pos = 256, .kohms = 100, },
+ [MCP423x_502] = { .wipers = 2, .max_pos = 128, .kohms = 5, },
+ [MCP423x_103] = { .wipers = 2, .max_pos = 128, .kohms = 10, },
+ [MCP423x_503] = { .wipers = 2, .max_pos = 128, .kohms = 50, },
+ [MCP423x_104] = { .wipers = 2, .max_pos = 128, .kohms = 100, },
+ [MCP424x_502] = { .wipers = 2, .max_pos = 128, .kohms = 5, },
+ [MCP424x_103] = { .wipers = 2, .max_pos = 128, .kohms = 10, },
+ [MCP424x_503] = { .wipers = 2, .max_pos = 128, .kohms = 50, },
+ [MCP424x_104] = { .wipers = 2, .max_pos = 128, .kohms = 100, },
+ [MCP425x_502] = { .wipers = 2, .max_pos = 256, .kohms = 5, },
+ [MCP425x_103] = { .wipers = 2, .max_pos = 256, .kohms = 10, },
+ [MCP425x_503] = { .wipers = 2, .max_pos = 256, .kohms = 50, },
+ [MCP425x_104] = { .wipers = 2, .max_pos = 256, .kohms = 100, },
+ [MCP426x_502] = { .wipers = 2, .max_pos = 256, .kohms = 5, },
+ [MCP426x_103] = { .wipers = 2, .max_pos = 256, .kohms = 10, },
+ [MCP426x_503] = { .wipers = 2, .max_pos = 256, .kohms = 50, },
+ [MCP426x_104] = { .wipers = 2, .max_pos = 256, .kohms = 100, },
+};
+
+struct mcp4131_data {
+ struct spi_device *spi;
+ const struct mcp4131_cfg *cfg;
+ struct mutex lock;
+ u8 buf[2] __aligned(IIO_DMA_MINALIGN);
+};
+
+#define MCP4131_CHANNEL(ch) { \
+ .type = IIO_RESISTANCE, \
+ .indexed = 1, \
+ .output = 1, \
+ .channel = (ch), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+}
+
+static const struct iio_chan_spec mcp4131_channels[] = {
+ MCP4131_CHANNEL(0),
+ MCP4131_CHANNEL(1),
+};
+
+static int mcp4131_read(struct spi_device *spi, void *buf, size_t len)
+{
+ struct spi_transfer t = {
+ .tx_buf = buf, /* We need to send addr, cmd and 12 bits */
+ .rx_buf = buf,
+ .len = len,
+ };
+ struct spi_message m;
+
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+
+ return spi_sync(spi, &m);
+}
+
+static int mcp4131_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ int err;
+ struct mcp4131_data *data = iio_priv(indio_dev);
+ int address = chan->channel;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&data->lock);
+
+ data->buf[0] = (address << MCP4131_WIPER_SHIFT) | MCP4131_READ;
+ data->buf[1] = 0;
+
+ err = mcp4131_read(data->spi, data->buf, 2);
+ if (err) {
+ mutex_unlock(&data->lock);
+ return err;
+ }
+
+ /* Error, bad address/command combination */
+ if (!MCP4131_CMDERR(data->buf)) {
+ mutex_unlock(&data->lock);
+ return -EIO;
+ }
+
+ *val = MCP4131_RAW(data->buf);
+ mutex_unlock(&data->lock);
+
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = 1000 * data->cfg->kohms;
+ *val2 = data->cfg->max_pos;
+ return IIO_VAL_FRACTIONAL;
+ }
+
+ return -EINVAL;
+}
+
+static int mcp4131_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ int err;
+ struct mcp4131_data *data = iio_priv(indio_dev);
+ int address = chan->channel << MCP4131_WIPER_SHIFT;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (val > data->cfg->max_pos || val < 0)
+ return -EINVAL;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ mutex_lock(&data->lock);
+
+ data->buf[0] = address << MCP4131_WIPER_SHIFT;
+ data->buf[0] |= MCP4131_WRITE | (val >> 8);
+ data->buf[1] = val & 0xFF; /* 8 bits here */
+
+ err = spi_write(data->spi, data->buf, 2);
+ mutex_unlock(&data->lock);
+
+ return err;
+}
+
+static const struct iio_info mcp4131_info = {
+ .read_raw = mcp4131_read_raw,
+ .write_raw = mcp4131_write_raw,
+};
+
+static int mcp4131_probe(struct spi_device *spi)
+{
+ int err;
+ struct device *dev = &spi->dev;
+ unsigned long devid;
+ struct mcp4131_data *data;
+ struct iio_dev *indio_dev;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ spi_set_drvdata(spi, indio_dev);
+ data->spi = spi;
+ data->cfg = device_get_match_data(&spi->dev);
+ if (!data->cfg) {
+ devid = spi_get_device_id(spi)->driver_data;
+ data->cfg = &mcp4131_cfg[devid];
+ }
+
+ mutex_init(&data->lock);
+
+ indio_dev->info = &mcp4131_info;
+ indio_dev->channels = mcp4131_channels;
+ indio_dev->num_channels = data->cfg->wipers;
+ indio_dev->name = spi_get_device_id(spi)->name;
+
+ err = devm_iio_device_register(dev, indio_dev);
+ if (err) {
+ dev_info(&spi->dev, "Unable to register %s\n", indio_dev->name);
+ return err;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id mcp4131_dt_ids[] = {
+ { .compatible = "microchip,mcp4131-502",
+ .data = &mcp4131_cfg[MCP413x_502] },
+ { .compatible = "microchip,mcp4131-103",
+ .data = &mcp4131_cfg[MCP413x_103] },
+ { .compatible = "microchip,mcp4131-503",
+ .data = &mcp4131_cfg[MCP413x_503] },
+ { .compatible = "microchip,mcp4131-104",
+ .data = &mcp4131_cfg[MCP413x_104] },
+ { .compatible = "microchip,mcp4132-502",
+ .data = &mcp4131_cfg[MCP413x_502] },
+ { .compatible = "microchip,mcp4132-103",
+ .data = &mcp4131_cfg[MCP413x_103] },
+ { .compatible = "microchip,mcp4132-503",
+ .data = &mcp4131_cfg[MCP413x_503] },
+ { .compatible = "microchip,mcp4132-104",
+ .data = &mcp4131_cfg[MCP413x_104] },
+ { .compatible = "microchip,mcp4141-502",
+ .data = &mcp4131_cfg[MCP414x_502] },
+ { .compatible = "microchip,mcp4141-103",
+ .data = &mcp4131_cfg[MCP414x_103] },
+ { .compatible = "microchip,mcp4141-503",
+ .data = &mcp4131_cfg[MCP414x_503] },
+ { .compatible = "microchip,mcp4141-104",
+ .data = &mcp4131_cfg[MCP414x_104] },
+ { .compatible = "microchip,mcp4142-502",
+ .data = &mcp4131_cfg[MCP414x_502] },
+ { .compatible = "microchip,mcp4142-103",
+ .data = &mcp4131_cfg[MCP414x_103] },
+ { .compatible = "microchip,mcp4142-503",
+ .data = &mcp4131_cfg[MCP414x_503] },
+ { .compatible = "microchip,mcp4142-104",
+ .data = &mcp4131_cfg[MCP414x_104] },
+ { .compatible = "microchip,mcp4151-502",
+ .data = &mcp4131_cfg[MCP415x_502] },
+ { .compatible = "microchip,mcp4151-103",
+ .data = &mcp4131_cfg[MCP415x_103] },
+ { .compatible = "microchip,mcp4151-503",
+ .data = &mcp4131_cfg[MCP415x_503] },
+ { .compatible = "microchip,mcp4151-104",
+ .data = &mcp4131_cfg[MCP415x_104] },
+ { .compatible = "microchip,mcp4152-502",
+ .data = &mcp4131_cfg[MCP415x_502] },
+ { .compatible = "microchip,mcp4152-103",
+ .data = &mcp4131_cfg[MCP415x_103] },
+ { .compatible = "microchip,mcp4152-503",
+ .data = &mcp4131_cfg[MCP415x_503] },
+ { .compatible = "microchip,mcp4152-104",
+ .data = &mcp4131_cfg[MCP415x_104] },
+ { .compatible = "microchip,mcp4161-502",
+ .data = &mcp4131_cfg[MCP416x_502] },
+ { .compatible = "microchip,mcp4161-103",
+ .data = &mcp4131_cfg[MCP416x_103] },
+ { .compatible = "microchip,mcp4161-503",
+ .data = &mcp4131_cfg[MCP416x_503] },
+ { .compatible = "microchip,mcp4161-104",
+ .data = &mcp4131_cfg[MCP416x_104] },
+ { .compatible = "microchip,mcp4162-502",
+ .data = &mcp4131_cfg[MCP416x_502] },
+ { .compatible = "microchip,mcp4162-103",
+ .data = &mcp4131_cfg[MCP416x_103] },
+ { .compatible = "microchip,mcp4162-503",
+ .data = &mcp4131_cfg[MCP416x_503] },
+ { .compatible = "microchip,mcp4162-104",
+ .data = &mcp4131_cfg[MCP416x_104] },
+ { .compatible = "microchip,mcp4231-502",
+ .data = &mcp4131_cfg[MCP423x_502] },
+ { .compatible = "microchip,mcp4231-103",
+ .data = &mcp4131_cfg[MCP423x_103] },
+ { .compatible = "microchip,mcp4231-503",
+ .data = &mcp4131_cfg[MCP423x_503] },
+ { .compatible = "microchip,mcp4231-104",
+ .data = &mcp4131_cfg[MCP423x_104] },
+ { .compatible = "microchip,mcp4232-502",
+ .data = &mcp4131_cfg[MCP423x_502] },
+ { .compatible = "microchip,mcp4232-103",
+ .data = &mcp4131_cfg[MCP423x_103] },
+ { .compatible = "microchip,mcp4232-503",
+ .data = &mcp4131_cfg[MCP423x_503] },
+ { .compatible = "microchip,mcp4232-104",
+ .data = &mcp4131_cfg[MCP423x_104] },
+ { .compatible = "microchip,mcp4241-502",
+ .data = &mcp4131_cfg[MCP424x_502] },
+ { .compatible = "microchip,mcp4241-103",
+ .data = &mcp4131_cfg[MCP424x_103] },
+ { .compatible = "microchip,mcp4241-503",
+ .data = &mcp4131_cfg[MCP424x_503] },
+ { .compatible = "microchip,mcp4241-104",
+ .data = &mcp4131_cfg[MCP424x_104] },
+ { .compatible = "microchip,mcp4242-502",
+ .data = &mcp4131_cfg[MCP424x_502] },
+ { .compatible = "microchip,mcp4242-103",
+ .data = &mcp4131_cfg[MCP424x_103] },
+ { .compatible = "microchip,mcp4242-503",
+ .data = &mcp4131_cfg[MCP424x_503] },
+ { .compatible = "microchip,mcp4242-104",
+ .data = &mcp4131_cfg[MCP424x_104] },
+ { .compatible = "microchip,mcp4251-502",
+ .data = &mcp4131_cfg[MCP425x_502] },
+ { .compatible = "microchip,mcp4251-103",
+ .data = &mcp4131_cfg[MCP425x_103] },
+ { .compatible = "microchip,mcp4251-503",
+ .data = &mcp4131_cfg[MCP425x_503] },
+ { .compatible = "microchip,mcp4251-104",
+ .data = &mcp4131_cfg[MCP425x_104] },
+ { .compatible = "microchip,mcp4252-502",
+ .data = &mcp4131_cfg[MCP425x_502] },
+ { .compatible = "microchip,mcp4252-103",
+ .data = &mcp4131_cfg[MCP425x_103] },
+ { .compatible = "microchip,mcp4252-503",
+ .data = &mcp4131_cfg[MCP425x_503] },
+ { .compatible = "microchip,mcp4252-104",
+ .data = &mcp4131_cfg[MCP425x_104] },
+ { .compatible = "microchip,mcp4261-502",
+ .data = &mcp4131_cfg[MCP426x_502] },
+ { .compatible = "microchip,mcp4261-103",
+ .data = &mcp4131_cfg[MCP426x_103] },
+ { .compatible = "microchip,mcp4261-503",
+ .data = &mcp4131_cfg[MCP426x_503] },
+ { .compatible = "microchip,mcp4261-104",
+ .data = &mcp4131_cfg[MCP426x_104] },
+ { .compatible = "microchip,mcp4262-502",
+ .data = &mcp4131_cfg[MCP426x_502] },
+ { .compatible = "microchip,mcp4262-103",
+ .data = &mcp4131_cfg[MCP426x_103] },
+ { .compatible = "microchip,mcp4262-503",
+ .data = &mcp4131_cfg[MCP426x_503] },
+ { .compatible = "microchip,mcp4262-104",
+ .data = &mcp4131_cfg[MCP426x_104] },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mcp4131_dt_ids);
+
+static const struct spi_device_id mcp4131_id[] = {
+ { "mcp4131-502", MCP413x_502 },
+ { "mcp4131-103", MCP413x_103 },
+ { "mcp4131-503", MCP413x_503 },
+ { "mcp4131-104", MCP413x_104 },
+ { "mcp4132-502", MCP413x_502 },
+ { "mcp4132-103", MCP413x_103 },
+ { "mcp4132-503", MCP413x_503 },
+ { "mcp4132-104", MCP413x_104 },
+ { "mcp4141-502", MCP414x_502 },
+ { "mcp4141-103", MCP414x_103 },
+ { "mcp4141-503", MCP414x_503 },
+ { "mcp4141-104", MCP414x_104 },
+ { "mcp4142-502", MCP414x_502 },
+ { "mcp4142-103", MCP414x_103 },
+ { "mcp4142-503", MCP414x_503 },
+ { "mcp4142-104", MCP414x_104 },
+ { "mcp4151-502", MCP415x_502 },
+ { "mcp4151-103", MCP415x_103 },
+ { "mcp4151-503", MCP415x_503 },
+ { "mcp4151-104", MCP415x_104 },
+ { "mcp4152-502", MCP415x_502 },
+ { "mcp4152-103", MCP415x_103 },
+ { "mcp4152-503", MCP415x_503 },
+ { "mcp4152-104", MCP415x_104 },
+ { "mcp4161-502", MCP416x_502 },
+ { "mcp4161-103", MCP416x_103 },
+ { "mcp4161-503", MCP416x_503 },
+ { "mcp4161-104", MCP416x_104 },
+ { "mcp4162-502", MCP416x_502 },
+ { "mcp4162-103", MCP416x_103 },
+ { "mcp4162-503", MCP416x_503 },
+ { "mcp4162-104", MCP416x_104 },
+ { "mcp4231-502", MCP423x_502 },
+ { "mcp4231-103", MCP423x_103 },
+ { "mcp4231-503", MCP423x_503 },
+ { "mcp4231-104", MCP423x_104 },
+ { "mcp4232-502", MCP423x_502 },
+ { "mcp4232-103", MCP423x_103 },
+ { "mcp4232-503", MCP423x_503 },
+ { "mcp4232-104", MCP423x_104 },
+ { "mcp4241-502", MCP424x_502 },
+ { "mcp4241-103", MCP424x_103 },
+ { "mcp4241-503", MCP424x_503 },
+ { "mcp4241-104", MCP424x_104 },
+ { "mcp4242-502", MCP424x_502 },
+ { "mcp4242-103", MCP424x_103 },
+ { "mcp4242-503", MCP424x_503 },
+ { "mcp4242-104", MCP424x_104 },
+ { "mcp4251-502", MCP425x_502 },
+ { "mcp4251-103", MCP425x_103 },
+ { "mcp4251-503", MCP425x_503 },
+ { "mcp4251-104", MCP425x_104 },
+ { "mcp4252-502", MCP425x_502 },
+ { "mcp4252-103", MCP425x_103 },
+ { "mcp4252-503", MCP425x_503 },
+ { "mcp4252-104", MCP425x_104 },
+ { "mcp4261-502", MCP426x_502 },
+ { "mcp4261-103", MCP426x_103 },
+ { "mcp4261-503", MCP426x_503 },
+ { "mcp4261-104", MCP426x_104 },
+ { "mcp4262-502", MCP426x_502 },
+ { "mcp4262-103", MCP426x_103 },
+ { "mcp4262-503", MCP426x_503 },
+ { "mcp4262-104", MCP426x_104 },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, mcp4131_id);
+
+static struct spi_driver mcp4131_driver = {
+ .driver = {
+ .name = "mcp4131",
+ .of_match_table = mcp4131_dt_ids,
+ },
+ .probe = mcp4131_probe,
+ .id_table = mcp4131_id,
+};
+
+module_spi_driver(mcp4131_driver);
+
+MODULE_AUTHOR("Slawomir Stepien <sst@poczta.fm>");
+MODULE_DESCRIPTION("MCP4131 digital potentiometer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/potentiometer/mcp4531.c b/drivers/iio/potentiometer/mcp4531.c
new file mode 100644
index 0000000000..f28880ebd7
--- /dev/null
+++ b/drivers/iio/potentiometer/mcp4531.c
@@ -0,0 +1,399 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Industrial I/O driver for Microchip digital potentiometers
+ * Copyright (c) 2015 Axentia Technologies AB
+ * Author: Peter Rosin <peda@axentia.se>
+ *
+ * Datasheet: http://www.microchip.com/downloads/en/DeviceDoc/22096b.pdf
+ *
+ * DEVID #Wipers #Positions Resistor Opts (kOhm) i2c address
+ * mcp4531 1 129 5, 10, 50, 100 010111x
+ * mcp4532 1 129 5, 10, 50, 100 01011xx
+ * mcp4541 1 129 5, 10, 50, 100 010111x
+ * mcp4542 1 129 5, 10, 50, 100 01011xx
+ * mcp4551 1 257 5, 10, 50, 100 010111x
+ * mcp4552 1 257 5, 10, 50, 100 01011xx
+ * mcp4561 1 257 5, 10, 50, 100 010111x
+ * mcp4562 1 257 5, 10, 50, 100 01011xx
+ * mcp4631 2 129 5, 10, 50, 100 0101xxx
+ * mcp4632 2 129 5, 10, 50, 100 01011xx
+ * mcp4641 2 129 5, 10, 50, 100 0101xxx
+ * mcp4642 2 129 5, 10, 50, 100 01011xx
+ * mcp4651 2 257 5, 10, 50, 100 0101xxx
+ * mcp4652 2 257 5, 10, 50, 100 01011xx
+ * mcp4661 2 257 5, 10, 50, 100 0101xxx
+ * mcp4662 2 257 5, 10, 50, 100 01011xx
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+
+#include <linux/iio/iio.h>
+
+struct mcp4531_cfg {
+ int wipers;
+ int avail[3];
+ int kohms;
+};
+
+enum mcp4531_type {
+ MCP453x_502,
+ MCP453x_103,
+ MCP453x_503,
+ MCP453x_104,
+ MCP454x_502,
+ MCP454x_103,
+ MCP454x_503,
+ MCP454x_104,
+ MCP455x_502,
+ MCP455x_103,
+ MCP455x_503,
+ MCP455x_104,
+ MCP456x_502,
+ MCP456x_103,
+ MCP456x_503,
+ MCP456x_104,
+ MCP463x_502,
+ MCP463x_103,
+ MCP463x_503,
+ MCP463x_104,
+ MCP464x_502,
+ MCP464x_103,
+ MCP464x_503,
+ MCP464x_104,
+ MCP465x_502,
+ MCP465x_103,
+ MCP465x_503,
+ MCP465x_104,
+ MCP466x_502,
+ MCP466x_103,
+ MCP466x_503,
+ MCP466x_104,
+};
+
+static const struct mcp4531_cfg mcp4531_cfg[] = {
+ [MCP453x_502] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 5, },
+ [MCP453x_103] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 10, },
+ [MCP453x_503] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 50, },
+ [MCP453x_104] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 100, },
+ [MCP454x_502] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 5, },
+ [MCP454x_103] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 10, },
+ [MCP454x_503] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 50, },
+ [MCP454x_104] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 100, },
+ [MCP455x_502] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 5, },
+ [MCP455x_103] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 10, },
+ [MCP455x_503] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 50, },
+ [MCP455x_104] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 100, },
+ [MCP456x_502] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 5, },
+ [MCP456x_103] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 10, },
+ [MCP456x_503] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 50, },
+ [MCP456x_104] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 100, },
+ [MCP463x_502] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 5, },
+ [MCP463x_103] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 10, },
+ [MCP463x_503] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 50, },
+ [MCP463x_104] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 100, },
+ [MCP464x_502] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 5, },
+ [MCP464x_103] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 10, },
+ [MCP464x_503] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 50, },
+ [MCP464x_104] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 100, },
+ [MCP465x_502] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 5, },
+ [MCP465x_103] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 10, },
+ [MCP465x_503] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 50, },
+ [MCP465x_104] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 100, },
+ [MCP466x_502] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 5, },
+ [MCP466x_103] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 10, },
+ [MCP466x_503] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 50, },
+ [MCP466x_104] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 100, },
+};
+
+#define MCP4531_WRITE (0 << 2)
+#define MCP4531_INCR (1 << 2)
+#define MCP4531_DECR (2 << 2)
+#define MCP4531_READ (3 << 2)
+
+#define MCP4531_WIPER_SHIFT (4)
+
+struct mcp4531_data {
+ struct i2c_client *client;
+ const struct mcp4531_cfg *cfg;
+};
+
+#define MCP4531_CHANNEL(ch) { \
+ .type = IIO_RESISTANCE, \
+ .indexed = 1, \
+ .output = 1, \
+ .channel = (ch), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_RAW), \
+}
+
+static const struct iio_chan_spec mcp4531_channels[] = {
+ MCP4531_CHANNEL(0),
+ MCP4531_CHANNEL(1),
+};
+
+static int mcp4531_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mcp4531_data *data = iio_priv(indio_dev);
+ int address = chan->channel << MCP4531_WIPER_SHIFT;
+ s32 ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = i2c_smbus_read_word_swapped(data->client,
+ MCP4531_READ | address);
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 1000 * data->cfg->kohms;
+ *val2 = data->cfg->avail[2];
+ return IIO_VAL_FRACTIONAL;
+ }
+
+ return -EINVAL;
+}
+
+static int mcp4531_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct mcp4531_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ *length = ARRAY_SIZE(data->cfg->avail);
+ *vals = data->cfg->avail;
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_RANGE;
+ }
+
+ return -EINVAL;
+}
+
+static int mcp4531_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct mcp4531_data *data = iio_priv(indio_dev);
+ int address = chan->channel << MCP4531_WIPER_SHIFT;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (val > data->cfg->avail[2] || val < 0)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return i2c_smbus_write_byte_data(data->client,
+ MCP4531_WRITE | address | (val >> 8),
+ val & 0xff);
+}
+
+static const struct iio_info mcp4531_info = {
+ .read_raw = mcp4531_read_raw,
+ .read_avail = mcp4531_read_avail,
+ .write_raw = mcp4531_write_raw,
+};
+
+#define MCP4531_ID_TABLE(_name, cfg) { \
+ .name = _name, \
+ .driver_data = (kernel_ulong_t)&mcp4531_cfg[cfg], \
+}
+
+static const struct i2c_device_id mcp4531_id[] = {
+ MCP4531_ID_TABLE("mcp4531-502", MCP453x_502),
+ MCP4531_ID_TABLE("mcp4531-103", MCP453x_103),
+ MCP4531_ID_TABLE("mcp4531-503", MCP453x_503),
+ MCP4531_ID_TABLE("mcp4531-104", MCP453x_104),
+ MCP4531_ID_TABLE("mcp4532-502", MCP453x_502),
+ MCP4531_ID_TABLE("mcp4532-103", MCP453x_103),
+ MCP4531_ID_TABLE("mcp4532-503", MCP453x_503),
+ MCP4531_ID_TABLE("mcp4532-104", MCP453x_104),
+ MCP4531_ID_TABLE("mcp4541-502", MCP454x_502),
+ MCP4531_ID_TABLE("mcp4541-103", MCP454x_103),
+ MCP4531_ID_TABLE("mcp4541-503", MCP454x_503),
+ MCP4531_ID_TABLE("mcp4541-104", MCP454x_104),
+ MCP4531_ID_TABLE("mcp4542-502", MCP454x_502),
+ MCP4531_ID_TABLE("mcp4542-103", MCP454x_103),
+ MCP4531_ID_TABLE("mcp4542-503", MCP454x_503),
+ MCP4531_ID_TABLE("mcp4542-104", MCP454x_104),
+ MCP4531_ID_TABLE("mcp4551-502", MCP455x_502),
+ MCP4531_ID_TABLE("mcp4551-103", MCP455x_103),
+ MCP4531_ID_TABLE("mcp4551-503", MCP455x_503),
+ MCP4531_ID_TABLE("mcp4551-104", MCP455x_104),
+ MCP4531_ID_TABLE("mcp4552-502", MCP455x_502),
+ MCP4531_ID_TABLE("mcp4552-103", MCP455x_103),
+ MCP4531_ID_TABLE("mcp4552-503", MCP455x_503),
+ MCP4531_ID_TABLE("mcp4552-104", MCP455x_104),
+ MCP4531_ID_TABLE("mcp4561-502", MCP456x_502),
+ MCP4531_ID_TABLE("mcp4561-103", MCP456x_103),
+ MCP4531_ID_TABLE("mcp4561-503", MCP456x_503),
+ MCP4531_ID_TABLE("mcp4561-104", MCP456x_104),
+ MCP4531_ID_TABLE("mcp4562-502", MCP456x_502),
+ MCP4531_ID_TABLE("mcp4562-103", MCP456x_103),
+ MCP4531_ID_TABLE("mcp4562-503", MCP456x_503),
+ MCP4531_ID_TABLE("mcp4562-104", MCP456x_104),
+ MCP4531_ID_TABLE("mcp4631-502", MCP463x_502),
+ MCP4531_ID_TABLE("mcp4631-103", MCP463x_103),
+ MCP4531_ID_TABLE("mcp4631-503", MCP463x_503),
+ MCP4531_ID_TABLE("mcp4631-104", MCP463x_104),
+ MCP4531_ID_TABLE("mcp4632-502", MCP463x_502),
+ MCP4531_ID_TABLE("mcp4632-103", MCP463x_103),
+ MCP4531_ID_TABLE("mcp4632-503", MCP463x_503),
+ MCP4531_ID_TABLE("mcp4632-104", MCP463x_104),
+ MCP4531_ID_TABLE("mcp4641-502", MCP464x_502),
+ MCP4531_ID_TABLE("mcp4641-103", MCP464x_103),
+ MCP4531_ID_TABLE("mcp4641-503", MCP464x_503),
+ MCP4531_ID_TABLE("mcp4641-104", MCP464x_104),
+ MCP4531_ID_TABLE("mcp4642-502", MCP464x_502),
+ MCP4531_ID_TABLE("mcp4642-103", MCP464x_103),
+ MCP4531_ID_TABLE("mcp4642-503", MCP464x_503),
+ MCP4531_ID_TABLE("mcp4642-104", MCP464x_104),
+ MCP4531_ID_TABLE("mcp4651-502", MCP465x_502),
+ MCP4531_ID_TABLE("mcp4651-103", MCP465x_103),
+ MCP4531_ID_TABLE("mcp4651-503", MCP465x_503),
+ MCP4531_ID_TABLE("mcp4651-104", MCP465x_104),
+ MCP4531_ID_TABLE("mcp4652-502", MCP465x_502),
+ MCP4531_ID_TABLE("mcp4652-103", MCP465x_103),
+ MCP4531_ID_TABLE("mcp4652-503", MCP465x_503),
+ MCP4531_ID_TABLE("mcp4652-104", MCP465x_104),
+ MCP4531_ID_TABLE("mcp4661-502", MCP466x_502),
+ MCP4531_ID_TABLE("mcp4661-103", MCP466x_103),
+ MCP4531_ID_TABLE("mcp4661-503", MCP466x_503),
+ MCP4531_ID_TABLE("mcp4661-104", MCP466x_104),
+ MCP4531_ID_TABLE("mcp4662-502", MCP466x_502),
+ MCP4531_ID_TABLE("mcp4662-103", MCP466x_103),
+ MCP4531_ID_TABLE("mcp4662-503", MCP466x_503),
+ MCP4531_ID_TABLE("mcp4662-104", MCP466x_104),
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, mcp4531_id);
+
+#define MCP4531_COMPATIBLE(of_compatible, cfg) { \
+ .compatible = of_compatible, \
+ .data = &mcp4531_cfg[cfg], \
+}
+
+static const struct of_device_id mcp4531_of_match[] = {
+ MCP4531_COMPATIBLE("microchip,mcp4531-502", MCP453x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4531-103", MCP453x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4531-503", MCP453x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4531-104", MCP453x_104),
+ MCP4531_COMPATIBLE("microchip,mcp4532-502", MCP453x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4532-103", MCP453x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4532-503", MCP453x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4532-104", MCP453x_104),
+ MCP4531_COMPATIBLE("microchip,mcp4541-502", MCP454x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4541-103", MCP454x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4541-503", MCP454x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4541-104", MCP454x_104),
+ MCP4531_COMPATIBLE("microchip,mcp4542-502", MCP454x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4542-103", MCP454x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4542-503", MCP454x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4542-104", MCP454x_104),
+ MCP4531_COMPATIBLE("microchip,mcp4551-502", MCP455x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4551-103", MCP455x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4551-503", MCP455x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4551-104", MCP455x_104),
+ MCP4531_COMPATIBLE("microchip,mcp4552-502", MCP455x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4552-103", MCP455x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4552-503", MCP455x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4552-104", MCP455x_104),
+ MCP4531_COMPATIBLE("microchip,mcp4561-502", MCP456x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4561-103", MCP456x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4561-503", MCP456x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4561-104", MCP456x_104),
+ MCP4531_COMPATIBLE("microchip,mcp4562-502", MCP456x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4562-103", MCP456x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4562-503", MCP456x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4562-104", MCP456x_104),
+ MCP4531_COMPATIBLE("microchip,mcp4631-502", MCP463x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4631-103", MCP463x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4631-503", MCP463x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4631-104", MCP463x_104),
+ MCP4531_COMPATIBLE("microchip,mcp4632-502", MCP463x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4632-103", MCP463x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4632-503", MCP463x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4632-104", MCP463x_104),
+ MCP4531_COMPATIBLE("microchip,mcp4641-502", MCP464x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4641-103", MCP464x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4641-503", MCP464x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4641-104", MCP464x_104),
+ MCP4531_COMPATIBLE("microchip,mcp4642-502", MCP464x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4642-103", MCP464x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4642-503", MCP464x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4642-104", MCP464x_104),
+ MCP4531_COMPATIBLE("microchip,mcp4651-502", MCP465x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4651-103", MCP465x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4651-503", MCP465x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4651-104", MCP465x_104),
+ MCP4531_COMPATIBLE("microchip,mcp4652-502", MCP465x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4652-103", MCP465x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4652-503", MCP465x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4652-104", MCP465x_104),
+ MCP4531_COMPATIBLE("microchip,mcp4661-502", MCP466x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4661-103", MCP466x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4661-503", MCP466x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4661-104", MCP466x_104),
+ MCP4531_COMPATIBLE("microchip,mcp4662-502", MCP466x_502),
+ MCP4531_COMPATIBLE("microchip,mcp4662-103", MCP466x_103),
+ MCP4531_COMPATIBLE("microchip,mcp4662-503", MCP466x_503),
+ MCP4531_COMPATIBLE("microchip,mcp4662-104", MCP466x_104),
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mcp4531_of_match);
+
+static int mcp4531_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct mcp4531_data *data;
+ struct iio_dev *indio_dev;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WORD_DATA)) {
+ dev_err(dev, "SMBUS Word Data not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+
+ data->cfg = i2c_get_match_data(client);
+
+ indio_dev->info = &mcp4531_info;
+ indio_dev->channels = mcp4531_channels;
+ indio_dev->num_channels = data->cfg->wipers;
+ indio_dev->name = client->name;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static struct i2c_driver mcp4531_driver = {
+ .driver = {
+ .name = "mcp4531",
+ .of_match_table = mcp4531_of_match,
+ },
+ .probe = mcp4531_probe,
+ .id_table = mcp4531_id,
+};
+
+module_i2c_driver(mcp4531_driver);
+
+MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
+MODULE_DESCRIPTION("MCP4531 digital potentiometer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/potentiometer/tpl0102.c b/drivers/iio/potentiometer/tpl0102.c
new file mode 100644
index 0000000000..8923ccb0fc
--- /dev/null
+++ b/drivers/iio/potentiometer/tpl0102.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * tpl0102.c - Support for Texas Instruments digital potentiometers
+ *
+ * Copyright (C) 2016, 2018
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ *
+ * TODO: enable/disable hi-z output control
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/iio/iio.h>
+
+struct tpl0102_cfg {
+ int wipers;
+ int avail[3];
+ int kohms;
+};
+
+enum tpl0102_type {
+ CAT5140_503,
+ CAT5140_104,
+ TPL0102_104,
+ TPL0401_103,
+};
+
+static const struct tpl0102_cfg tpl0102_cfg[] = {
+ /* on-semiconductor parts */
+ [CAT5140_503] = { .wipers = 1, .avail = { 0, 1, 255 }, .kohms = 50, },
+ [CAT5140_104] = { .wipers = 1, .avail = { 0, 1, 255 }, .kohms = 100, },
+ /* ti parts */
+ [TPL0102_104] = { .wipers = 2, .avail = { 0, 1, 255 }, .kohms = 100 },
+ [TPL0401_103] = { .wipers = 1, .avail = { 0, 1, 127 }, .kohms = 10, },
+};
+
+struct tpl0102_data {
+ struct regmap *regmap;
+ const struct tpl0102_cfg *cfg;
+};
+
+static const struct regmap_config tpl0102_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+#define TPL0102_CHANNEL(ch) { \
+ .type = IIO_RESISTANCE, \
+ .indexed = 1, \
+ .output = 1, \
+ .channel = (ch), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), \
+}
+
+static const struct iio_chan_spec tpl0102_channels[] = {
+ TPL0102_CHANNEL(0),
+ TPL0102_CHANNEL(1),
+};
+
+static int tpl0102_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct tpl0102_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW: {
+ int ret = regmap_read(data->regmap, chan->channel, val);
+
+ return ret ? ret : IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ *val = 1000 * data->cfg->kohms;
+ *val2 = data->cfg->avail[2] + 1;
+ return IIO_VAL_FRACTIONAL;
+ }
+
+ return -EINVAL;
+}
+
+static int tpl0102_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct tpl0102_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ *length = ARRAY_SIZE(data->cfg->avail);
+ *vals = data->cfg->avail;
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_RANGE;
+ }
+
+ return -EINVAL;
+}
+
+static int tpl0102_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct tpl0102_data *data = iio_priv(indio_dev);
+
+ if (mask != IIO_CHAN_INFO_RAW)
+ return -EINVAL;
+
+ if (val > data->cfg->avail[2] || val < 0)
+ return -EINVAL;
+
+ return regmap_write(data->regmap, chan->channel, val);
+}
+
+static const struct iio_info tpl0102_info = {
+ .read_raw = tpl0102_read_raw,
+ .read_avail = tpl0102_read_avail,
+ .write_raw = tpl0102_write_raw,
+};
+
+static int tpl0102_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct device *dev = &client->dev;
+ struct tpl0102_data *data;
+ struct iio_dev *indio_dev;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+
+ data->cfg = &tpl0102_cfg[id->driver_data];
+ data->regmap = devm_regmap_init_i2c(client, &tpl0102_regmap_config);
+ if (IS_ERR(data->regmap)) {
+ dev_err(dev, "regmap initialization failed\n");
+ return PTR_ERR(data->regmap);
+ }
+
+ indio_dev->info = &tpl0102_info;
+ indio_dev->channels = tpl0102_channels;
+ indio_dev->num_channels = data->cfg->wipers;
+ indio_dev->name = client->name;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct i2c_device_id tpl0102_id[] = {
+ { "cat5140-503", CAT5140_503 },
+ { "cat5140-104", CAT5140_104 },
+ { "tpl0102-104", TPL0102_104 },
+ { "tpl0401-103", TPL0401_103 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, tpl0102_id);
+
+static struct i2c_driver tpl0102_driver = {
+ .driver = {
+ .name = "tpl0102",
+ },
+ .probe = tpl0102_probe,
+ .id_table = tpl0102_id,
+};
+
+module_i2c_driver(tpl0102_driver);
+
+MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
+MODULE_DESCRIPTION("TPL0102 digital potentiometer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/potentiometer/x9250.c b/drivers/iio/potentiometer/x9250.c
new file mode 100644
index 0000000000..7353484926
--- /dev/null
+++ b/drivers/iio/potentiometer/x9250.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *
+ * x9250.c -- Renesas X9250 potentiometers IIO driver
+ *
+ * Copyright 2023 CS GROUP France
+ *
+ * Author: Herve Codina <herve.codina@bootlin.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+
+struct x9250_cfg {
+ const char *name;
+ int kohms;
+};
+
+struct x9250 {
+ struct spi_device *spi;
+ const struct x9250_cfg *cfg;
+ struct gpio_desc *wp_gpio;
+};
+
+#define X9250_ID 0x50
+#define X9250_CMD_RD_WCR(_p) (0x90 | (_p))
+#define X9250_CMD_WR_WCR(_p) (0xa0 | (_p))
+
+static int x9250_write8(struct x9250 *x9250, u8 cmd, u8 val)
+{
+ u8 txbuf[3];
+
+ txbuf[0] = X9250_ID;
+ txbuf[1] = cmd;
+ txbuf[2] = val;
+
+ return spi_write_then_read(x9250->spi, txbuf, ARRAY_SIZE(txbuf), NULL, 0);
+}
+
+static int x9250_read8(struct x9250 *x9250, u8 cmd, u8 *val)
+{
+ u8 txbuf[2];
+
+ txbuf[0] = X9250_ID;
+ txbuf[1] = cmd;
+
+ return spi_write_then_read(x9250->spi, txbuf, ARRAY_SIZE(txbuf), val, 1);
+}
+
+#define X9250_CHANNEL(ch) { \
+ .type = IIO_RESISTANCE, \
+ .indexed = 1, \
+ .output = 1, \
+ .channel = (ch), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_RAW), \
+}
+
+static const struct iio_chan_spec x9250_channels[] = {
+ X9250_CHANNEL(0),
+ X9250_CHANNEL(1),
+ X9250_CHANNEL(2),
+ X9250_CHANNEL(3),
+};
+
+static int x9250_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct x9250 *x9250 = iio_priv(indio_dev);
+ int ch = chan->channel;
+ int ret;
+ u8 v;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = x9250_read8(x9250, X9250_CMD_RD_WCR(ch), &v);
+ if (ret)
+ return ret;
+ *val = v;
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = 1000 * x9250->cfg->kohms;
+ *val2 = U8_MAX;
+ return IIO_VAL_FRACTIONAL;
+ }
+
+ return -EINVAL;
+}
+
+static int x9250_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length, long mask)
+{
+ static const int range[] = {0, 1, 255}; /* min, step, max */
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ *length = ARRAY_SIZE(range);
+ *vals = range;
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_RANGE;
+ }
+
+ return -EINVAL;
+}
+
+static int x9250_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct x9250 *x9250 = iio_priv(indio_dev);
+ int ch = chan->channel;
+ int ret;
+
+ if (mask != IIO_CHAN_INFO_RAW)
+ return -EINVAL;
+
+ if (val > U8_MAX || val < 0)
+ return -EINVAL;
+
+ gpiod_set_value_cansleep(x9250->wp_gpio, 0);
+ ret = x9250_write8(x9250, X9250_CMD_WR_WCR(ch), val);
+ gpiod_set_value_cansleep(x9250->wp_gpio, 1);
+
+ return ret;
+}
+
+static const struct iio_info x9250_info = {
+ .read_raw = x9250_read_raw,
+ .read_avail = x9250_read_avail,
+ .write_raw = x9250_write_raw,
+};
+
+enum x9250_type {
+ X9250T,
+ X9250U,
+};
+
+static const struct x9250_cfg x9250_cfg[] = {
+ [X9250T] = { .name = "x9250t", .kohms = 100, },
+ [X9250U] = { .name = "x9250u", .kohms = 50, },
+};
+
+static const char *const x9250_regulator_names[] = {
+ "vcc",
+ "avp",
+ "avn",
+};
+
+static int x9250_probe(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev;
+ struct x9250 *x9250;
+ int ret;
+
+ ret = devm_regulator_bulk_get_enable(&spi->dev, ARRAY_SIZE(x9250_regulator_names),
+ x9250_regulator_names);
+ if (ret)
+ return dev_err_probe(&spi->dev, ret, "Failed to get regulators\n");
+
+ /*
+ * The x9250 needs a 5ms maximum delay after the power-supplies are set
+ * before performing the first write (1ms for the first read).
+ */
+ usleep_range(5000, 6000);
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*x9250));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ x9250 = iio_priv(indio_dev);
+ x9250->spi = spi;
+ x9250->cfg = spi_get_device_match_data(spi);
+ x9250->wp_gpio = devm_gpiod_get_optional(&spi->dev, "wp", GPIOD_OUT_LOW);
+ if (IS_ERR(x9250->wp_gpio))
+ return dev_err_probe(&spi->dev, PTR_ERR(x9250->wp_gpio),
+ "failed to get wp gpio\n");
+
+ indio_dev->info = &x9250_info;
+ indio_dev->channels = x9250_channels;
+ indio_dev->num_channels = ARRAY_SIZE(x9250_channels);
+ indio_dev->name = x9250->cfg->name;
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct of_device_id x9250_of_match[] = {
+ { .compatible = "renesas,x9250t", .data = &x9250_cfg[X9250T]},
+ { .compatible = "renesas,x9250u", .data = &x9250_cfg[X9250U]},
+ { }
+};
+MODULE_DEVICE_TABLE(of, x9250_of_match);
+
+static const struct spi_device_id x9250_id_table[] = {
+ { "x9250t", (kernel_ulong_t)&x9250_cfg[X9250T] },
+ { "x9250u", (kernel_ulong_t)&x9250_cfg[X9250U] },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, x9250_id_table);
+
+static struct spi_driver x9250_spi_driver = {
+ .driver = {
+ .name = "x9250",
+ .of_match_table = x9250_of_match,
+ },
+ .id_table = x9250_id_table,
+ .probe = x9250_probe,
+};
+
+module_spi_driver(x9250_spi_driver);
+
+MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
+MODULE_DESCRIPTION("X9250 ALSA SoC driver");
+MODULE_LICENSE("GPL");