[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20230519160145.44208-3-marius.cristea@microchip.com>
Date: Fri, 19 May 2023 19:01:45 +0300
From: <marius.cristea@...rochip.com>
To: <jic23@...nel.org>, <lars@...afoo.de>, <robh+dt@...nel.org>
CC: <linux-iio@...r.kernel.org>, <devicetree@...r.kernel.org>,
<linux-kernel@...r.kernel.org>, <marius.cristea@...rochip.com>
Subject: [PATCH 2/2] iio: adc: adding support for MCP3564 ADC
From: Marius Cristea <marius.cristea@...rochip.com>
This is the iio driver for Microchip family of 153.6 ksps,
Low-Noise 16/24-Bit Delta-Sigma ADCs with an SPI interface.
Signed-off-by: Marius Cristea <marius.cristea@...rochip.com>
---
.../ABI/testing/sysfs-bus-iio-mcp3564 | 143 ++
MAINTAINERS | 7 +
drivers/iio/adc/Kconfig | 13 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/mcp3564.c | 1395 +++++++++++++++++
5 files changed, 1559 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-mcp3564
create mode 100644 drivers/iio/adc/mcp3564.c
diff --git a/Documentation/ABI/testing/sysfs-bus-iio-mcp3564 b/Documentation/ABI/testing/sysfs-bus-iio-mcp3564
new file mode 100644
index 000000000000..fd65995e2245
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-iio-mcp3564
@@ -0,0 +1,143 @@
+What: /sys/bus/iio/devices/iio:deviceX/calibbias
+KernelVersion: 6.4
+Contact: linux-iio@...r.kernel.org
+Description:
+ Offset Error Digital Calibration Code (two’s complement,
+ MSb first coding). The device includes a digital calibration
+ feature for offset and gain errors. The calibration
+ scheme for offset error consists of the addition of a
+ fixed offset value to the ADC output code
+
+ This attribute is used to set the offset error digital
+ calibration code (two’s complement, MSb first coding).
+ The ADC includes a digital calibration feature for offset
+ errors. The calibration scheme for offset error consists of the
+ addition of a fixed offset value to the ADC output code.
+
+What: /sys/bus/iio/devices/iio:deviceX/calibbias_available
+KernelVersion: 6.4
+Contact: linux-iio@...r.kernel.org
+Description:
+ Reading returns a range with the possible values for the offset
+ error digital calibration register.
+
+What: /sys/bus/iio/devices/iio:deviceX/calibscale
+KernelVersion: 6.4
+Contact: linux-iio@...r.kernel.org
+Description:
+ This attribute is used to set the gain error digital calibration
+ code (unsigned, MSb first coding). The default value is 800000,
+ which provides a gain of 1x.
+ The ADC includes a digital calibration feature for gain errors.
+ The calibration scheme for gain error consists of the
+ multiplication of a fixed gain value to the ADC data code.
+
+What: /sys/bus/iio/devices/iio:deviceX/calibscale_available
+KernelVersion: 6.4
+Contact: linux-iio@...r.kernel.org
+Description:
+ Reading returns a range with the possible values for the gain
+ error digital calibration register.
+
+What: /sys/bus/iio/devices/iio:deviceX/boost_current
+KernelVersion: 6.4
+Contact: linux-iio@...r.kernel.org
+Description:
+ This attribute is used to set the biasing circuit of the
+ Delta-Sigma modulator. The different BOOST settings are applied
+ to the entire modulator circuit, including the voltage reference
+ buffers.
+
+What: /sys/bus/iio/devices/iio:deviceX/boost_current_available
+KernelVersion: 6.4
+Contact: linux-iio@...r.kernel.org
+Description:
+ Reading returns a list with the possible values for
+ the current biasing circuit of the Delta-Sigma modulator.
+
+ "x0.5", - ADC channel has current x 0.5
+
+ "x0.66", - ADC channel has current x 0.66
+
+ "x1", - ADC channel has current x 1 (default)
+
+ "x2" - ADC channel has current x 2
+
+What: /sys/bus/iio/devices/iio:deviceX/current_bias
+KernelVersion: 6.4
+Contact: linux-iio@...r.kernel.org
+Description:
+ This attribute is used to set the current Source/Sink
+ values for sensor bias. The ADC inputs, VIN-/VIN+, feature a
+ selectable burnout current source, which enables open or
+ short-circuit detection, as well as biasing very low-current
+ external sensors. The bias current is sourced on the VIN+ pin of
+ the ADC (noninverting output of the analog multiplexer)
+ and sunk on the VIN- pin of the ADC (inverting output of
+ the analog multiplexer). Since the same current flows
+ at the VIN-/VIN+ pins of the ADC, it can sense the
+ impedance of an externally connected sensor that
+ would be connected between the selected inputs of the
+ multiplexer.
+
+What: /sys/bus/iio/devices/iio:deviceX/current_bias_available
+KernelVersion: 6.4
+Contact: linux-iio@...r.kernel.org
+Description:
+ Reading returns a list with the possible values for
+ the current source/sink selection values for sensor bias.
+
+ "15_uA" - 15 uA is applied to the ADC inputs.
+
+ "3.7_uA" - 3.7 uA is applied to the ADC inputs.
+
+ "0.9_uA" - 0.9 uA is applied to the ADC inputs.
+
+ "no_current" - "No current source is applied to the ADC
+ inputs (default)"
+
+What: /sys/bus/iio/devices/iio:deviceX/enable_auto_zeroing_mux
+KernelVersion: 6.4
+Contact: linux-iio@...r.kernel.org
+Description:
+ This attribute is used to enable the analog input multiplexer
+ auto-zeroing algorithm. Write '1' to enable it, write '0' to
+ disable it.
+
+What: /sys/bus/iio/devices/iio:deviceX/enable_auto_zeroing_ref
+KernelVersion: 6.4
+Contact: linux-iio@...r.kernel.org
+Description:
+ This attribute is used to enable the chopping algorithm for the
+ internal voltage reference buffer. Write '1' to enable it,
+ write '0' to disable it.
+
+What: /sys/bus/iio/devices/iio:deviceX/hardwaregain
+KernelVersion: 6.4
+Contact: linux-iio@...r.kernel.org
+Description:
+ This attribute is used to set the hardware applied gain factor.
+ It is shared across all channels.
+
+What: /sys/bus/iio/devices/iio:deviceX/hardwaregain_available
+KernelVersion: 6.4
+Contact: linux-iio@...r.kernel.org
+Description:
+ Reading this attribute will list all available hardware applied gain factors.
+ Shared across all channels.
+
+ "0.33" - Gain is x1/3 ( x0.33 analog)
+
+ "1" - Gain is x1 ( x8 analog) (default)
+
+ "2" - Gain is x2 ( x2 analog)
+
+ "4" - Gain is x4 ( x4 analog)
+
+ "8" - Gain is x8 ( x8 analog)
+
+ "16" - Gain is x16 (x16 analog)
+
+ "32" - Gain is x32 (x16 analog, x2 digital)
+
+ "64" - Gain is x64 (x16 analog, x4 digital)
\ No newline at end of file
diff --git a/MAINTAINERS b/MAINTAINERS
index 7e0b87d5aa2e..2fd2d06fb87f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13786,6 +13786,13 @@ S: Supported
F: Documentation/devicetree/bindings/regulator/mcp16502-regulator.txt
F: drivers/regulator/mcp16502.c
+MICROCHIP MCP3564 ADC DRIVER
+M: Marius Cristea <marius.cristea@...rochip.com>
+L: linux-iio@...r.kernel.org
+S: Supported
+F: Documentation/devicetree/bindings/iio/adc/microchip,mcp3564.yaml
+F: drivers/iio/adc/mcp3564.c
+
MICROCHIP MCP3911 ADC DRIVER
M: Marcus Folkesson <marcus.folkesson@...il.com>
M: Kent Gustavsson <kent@...oris.se>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index eb2b09ef5d5b..9ee85764b985 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -768,6 +768,19 @@ config MCP3422
This driver can also be built as a module. If so, the module will be
called mcp3422.
+config MCP3564
+ tristate "Microchip Technology MCP3461/2/4/R, MCP3561/2/4/R driver"
+ depends on SPI
+ depends on IIO
+ help
+ Say yes here to build support for Microchip Technology's MCP3461,
+ MCP3462, MCP3464, MCP3461R, MCP3462R, MCP3464R, MCP3561, MCP3562,
+ MCP3564, MCP3561R, MCP3562R and MCP3564R analog to digital
+ converters.
+
+ This driver can also be built as a module. If so, the module will be
+ called mcp3564.
+
config MCP3911
tristate "Microchip Technology MCP3911 driver"
depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index e07e4a3e6237..78aed4878e86 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -70,6 +70,7 @@ obj-$(CONFIG_MAX1363) += max1363.o
obj-$(CONFIG_MAX9611) += max9611.o
obj-$(CONFIG_MCP320X) += mcp320x.o
obj-$(CONFIG_MCP3422) += mcp3422.o
+obj-$(CONFIG_MCP356X) += mcp3564.o
obj-$(CONFIG_MCP3911) += mcp3911.o
obj-$(CONFIG_MEDIATEK_MT6360_ADC) += mt6360-adc.o
obj-$(CONFIG_MEDIATEK_MT6370_ADC) += mt6370-adc.o
diff --git a/drivers/iio/adc/mcp3564.c b/drivers/iio/adc/mcp3564.c
new file mode 100644
index 000000000000..dfdbe4e37cd4
--- /dev/null
+++ b/drivers/iio/adc/mcp3564.c
@@ -0,0 +1,1395 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * IIO driver for MCP356X/MCP356XR and MCP346X/MCP346XR series ADC chip family
+ *
+ * Copyright (C) 2022-2023 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Marius Cristea <marius.cristea@...rochip.com>
+ *
+ * Datasheet for MCP3561, MCP3562, MCP3564 can be found here:
+ * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP3561-2-4-Family-Data-Sheet-DS20006181C.pdf
+ * Datasheet for MCP3561R, MCP3562R, MCP3564R can be found here:
+ * https://ww1.microchip.com/downloads/aemDocuments/documents/APID/ProductDocuments/DataSheets/MCP3561_2_4R-Data-Sheet-DS200006391C.pdf
+ * Datasheet for MCP3461, MCP3462, MCP3464 can be found here:
+ * https://ww1.microchip.com/downloads/aemDocuments/documents/APID/ProductDocuments/DataSheets/MCP3461-2-4-Two-Four-Eight-Channel-153.6-ksps-Low-Noise-16-Bit-Delta-Sigma-ADC-Data-Sheet-20006180D.pdf
+ * Datasheet for MCP3461R, MCP3462R, MCP3464R can be found here:
+ * https://ww1.microchip.com/downloads/aemDocuments/documents/APID/ProductDocuments/DataSheets/MCP3461-2-4R-Family-Data-Sheet-DS20006404C.pdf
+ *
+ */
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/iopoll.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/util_macros.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define MCP356X_ADCDATA 0x00
+#define MCP356X_CONFIG0 0x01
+#define MCP356X_CONFIG1 0x02
+#define MCP356X_CONFIG2 0x03
+#define MCP356X_CONFIG3 0x04
+#define MCP356X_IRQ 0x05
+#define MCP356X_MUX 0x06
+#define MCP356X_SCAN 0x07
+#define MCP356X_TIMER 0x08
+#define MCP356X_OFFSETCAL 0x09
+#define MCP356X_GAINCAL 0x0A
+#define MCP356X_RESERVED_B 0x0B
+#define MCP356X_RESERVED_C 0x0C
+#define MCP356X_LOCK 0x0D
+#define MCP356X_RESERVED_E 0x0E
+#define MCP356X_CRCCFG 0x0F
+
+#define MCP356X_FAST_CMD_CTRL 0
+#define MCP356X_RD_CTRL BIT(0)
+#define MCP356X_WRT_CTRL BIT(1)
+
+#define MCP356X_FULL_RESET_CMD GENMASK(3, 1)
+
+#define MCP3461_HW_ID BIT(3)
+#define MCP3462_HW_ID 0x0009
+#define MCP3464_HW_ID 0x000B
+
+#define MCP3561_HW_ID GENMASK(3, 2)
+#define MCP3562_HW_ID 0x000D
+#define MCP3564_HW_ID GENMASK(3, 0)
+#define MCP356X_HW_ID_MASK GENMASK(3, 0)
+
+#define MCP356XR_INT_VREF_MV 2400
+
+/* MUX_VIN Input Selection
+ */
+#define MCP356X_INTERNAL_VCM GENMASK(3, 0)
+#define MCP356X_TEMP_DIODE_M GENMASK(3, 1)
+#define MCP356X_TEMP_DIODE_P 0b1101
+#define MCP356X_REFIN_NEG GENMASK(3, 2)
+#define MCP356X_REFIN_POZ 0b1011
+#define MCP356X_RESERVED 0b1010 /* do not use */
+#define MCP356X_AVDD 0b1001
+#define MCP356X_AGND BIT(3)
+#define MCP356X_CH7 GENMASK(2, 0)
+#define MCP356X_CH6 GENMASK(2, 1)
+#define MCP356X_CH5 0b0101
+#define MCP356X_CH4 BIT(2)
+#define MCP356X_CH3 GENMASK(1, 0)
+#define MCP356X_CH2 BIT(1)
+#define MCP356X_CH1 BIT(0)
+#define MCP356X_CH0 0
+
+#define MCP356X_ADC_MODE_MASK GENMASK(1, 0)
+
+#define MCP356X_ADC_DEFAULT_MODE 0
+#define MCP356X_ADC_SHUTDOWN_MODE BIT(0)
+#define MCP356X_ADC_STANDBY BIT(1)
+#define MCP356X_ADC_CONVERSION_MODE GENMASK(1, 0)
+
+#define MCP356X_DATA_READY_MASK BIT(6)
+
+#define MCP356X_OVERSAMPLING_RATIO_32 0
+#define MCP356X_OVERSAMPLING_RATIO_64 BIT(0)
+#define MCP356X_OVERSAMPLING_RATIO_128 BIT(1)
+#define MCP356X_OVERSAMPLING_RATIO_256 GENMASK(1, 0)
+#define MCP356X_OVERSAMPLING_RATIO_512 BIT(2)
+#define MCP356X_OVERSAMPLING_RATIO_1024 0x05
+#define MCP356X_OVERSAMPLING_RATIO_2048 GENMASK(2, 1)
+#define MCP356X_OVERSAMPLING_RATIO_4096 GENMASK(2, 0)
+#define MCP356X_OVERSAMPLING_RATIO_8192 BIT(3)
+#define MCP356X_OVERSAMPLING_RATIO_16384 0x09
+#define MCP356X_OVERSAMPLING_RATIO_20480 0x0A
+#define MCP356X_OVERSAMPLING_RATIO_24576 0x0B
+#define MCP356X_OVERSAMPLING_RATIO_40960 0x0C
+#define MCP356X_OVERSAMPLING_RATIO_49152 0x0D
+#define MCP356X_OVERSAMPLING_RATIO_81920 GENMASK(3, 1)
+#define MCP356X_OVERSAMPLING_RATIO_98304 GENMASK(3, 0)
+
+#define MCP356X_OVERSAMPLING_RATIO_MASK GENMASK(5, 2)
+#define MCP356X_OVERSAMPLING_RATIO_SHIFT 0x02
+
+#define MCP356X_HARDWARE_GAIN_MASK GENMASK(5, 3)
+#define MCP356X_HARDWARE_GAIN_SHIFT 0x03
+#define MCP356X_DEFAULT_HARDWARE_GAIN BIT(1)
+
+#define MCP356X_CS_SEL_0_0_uA 0x0
+#define MCP356X_CS_SEL_0_9_uA BIT(0)
+#define MCP356X_CS_SEL_3_7_uA BIT(1)
+#define MCP356X_CS_SEL_15_uA GENMASK(1, 0)
+
+#define MCP356X_CS_SEL_MASK GENMASK(3, 2)
+
+#define MCP356X_BOOST_CURRENT_x0_50 0
+#define MCP356X_BOOST_CURRENT_x0_66 BIT(0)
+#define MCP356X_BOOST_CURRENT_x1_00 BIT(1)
+#define MCP356X_BOOST_CURRENT_x2_00 GENMASK(1, 0)
+
+#define MCP356X_BOOST_CURRENT_MASK GENMASK(7, 6)
+
+/* Auto-Zeroing MUX Setting */
+#define MCP356X_AZ_MUX_MASK BIT(2)
+/* Auto-Zeroing REF Setting */
+#define MCP356X_AZ_REF_MASK BIT(1)
+
+#define MCP356X_SHARED_DEVATTRS_COUNT 1
+#define MCP356X_PARTICULAR_DEVATTRS_COUNT 1
+
+#define MAX_HWGAIN 64000
+
+#define MCP356X_DATA_READY_TIMEOUT_MS 2000
+
+enum mcp356x_ids {
+ mcp3461,
+ mcp3462,
+ mcp3464,
+ mcp3461r,
+ mcp3462r,
+ mcp3464r,
+ mcp3561,
+ mcp3562,
+ mcp3564,
+ mcp3561r,
+ mcp3562r,
+ mcp3564r,
+};
+
+static const unsigned int mcp356x_oversampling_avail[16] = {
+ [MCP356X_OVERSAMPLING_RATIO_32] = 32,
+ [MCP356X_OVERSAMPLING_RATIO_64] = 64,
+ [MCP356X_OVERSAMPLING_RATIO_128] = 128,
+ [MCP356X_OVERSAMPLING_RATIO_256] = 256,
+ [MCP356X_OVERSAMPLING_RATIO_512] = 512,
+ [MCP356X_OVERSAMPLING_RATIO_1024] = 1024,
+ [MCP356X_OVERSAMPLING_RATIO_2048] = 2048,
+ [MCP356X_OVERSAMPLING_RATIO_4096] = 4096,
+ [MCP356X_OVERSAMPLING_RATIO_8192] = 8192,
+ [MCP356X_OVERSAMPLING_RATIO_16384] = 16384,
+ [MCP356X_OVERSAMPLING_RATIO_20480] = 20480,
+ [MCP356X_OVERSAMPLING_RATIO_24576] = 24576,
+ [MCP356X_OVERSAMPLING_RATIO_40960] = 40960,
+ [MCP356X_OVERSAMPLING_RATIO_49152] = 49152,
+ [MCP356X_OVERSAMPLING_RATIO_81920] = 81920,
+ [MCP356X_OVERSAMPLING_RATIO_98304] = 98304
+};
+
+/*
+ * Current Source/Sink Selection Bits for Sensor Bias (source on VIN+/sink on VIN-)
+ */
+static const char * const mcp356x_current_bias_avail[] = {
+ [MCP356X_CS_SEL_0_0_uA] = "no_current(default)",
+ [MCP356X_CS_SEL_0_9_uA] = "0.9_uA",
+ [MCP356X_CS_SEL_3_7_uA] = "3.7_uA",
+ [MCP356X_CS_SEL_15_uA] = "15_uA",
+};
+
+/*
+ * BOOST[1:0]: ADC Bias Current Selection
+ */
+static const char * const mcp356x_boost_current_avail[] = {
+ [MCP356X_BOOST_CURRENT_x0_50] = "x0.5",
+ [MCP356X_BOOST_CURRENT_x0_66] = "x0.66",
+ [MCP356X_BOOST_CURRENT_x1_00] = "x1_(default)",
+ [MCP356X_BOOST_CURRENT_x2_00] = "x2",
+};
+
+/*
+ * Calibration bias values
+ */
+static const int mcp356x_calib_bias[] = {
+ -8388608, /* min: -2^23 */
+ 1, /* step: 1 */
+ 8388607 /* max: 2^23 - 1 */
+};
+
+/*
+ * Calibration scale values
+ * The Gain Error Calibration register (GAINCAL) is an
+ * unsigned 24-bit register that holds the digital gain error
+ * calibration value, GAINCAL which could be calculated by
+ * GAINCAL (V/V) = (GAINCAL[23:0])/8388608
+ * The gain error calibration value range in equivalent voltage is [0; 2-2^(-23)]
+ */
+static const unsigned int mcp356x_calib_scale[] = {
+ 0, /* min: 0 */
+ 1, /* step: 1/8388608 */
+ 16777215 /* max: 2 - 2^(-23) */
+};
+
+/* Programmable hardware gain x1/3, x1, x2, x4, x8, x16, x32, x64 */
+static const int mcp356x_hwgain_frac[] = {
+ 3,
+ 10,
+ 1,
+ 1,
+ 2,
+ 1,
+ 4,
+ 1,
+ 8,
+ 1,
+ 16,
+ 1,
+ 32,
+ 1,
+ 64,
+ 1
+};
+
+static const int mcp356x_hwgain[] = {
+ 300,
+ 1000,
+ 2000,
+ 4000,
+ 8000,
+ 16000,
+ 32000,
+ 64000
+};
+
+/**
+ * struct mcp356x_chip_info - chip specific data
+ * @channels: struct iio_chan_spec matching the device's capabilities
+ * @num_channels: number of channels
+ * @int_vref_uv: internal voltage reference value in microVolts
+ * @has_vref: Does the ADC has an internal voltage reference?
+ */
+struct mcp356x_chip_info {
+ const struct iio_chan_spec *channels;
+ unsigned int num_channels;
+ unsigned int int_vref_uv;
+ bool has_vref;
+};
+
+/**
+ * struct mcp356x_state - working data for a ADC device
+ * @chip_info: chip specific data
+ * @mcp356x_info: information about iio device
+ * @spi: SPI device structure
+ * @vref: The regulator device used as a voltage reference in case
+ * external voltage reference is used
+ * @vref_mv: voltage reference value in miliVolts
+ * @lock: mutex to prevent concurrent reads/writes
+ * @dev_addr: hardware device address
+ * @oversampling: the index inside oversampling list of the ADC
+ * @hwgain: the index inside hardware gain list of the ADC
+ * @calib_bias: calibration bias value
+ * @calib_scale: calibration scale value
+ * @current_boost_mode: the index inside current boost list of the ADC
+ * @current_bias_mode: the index inside current bias list of the ADC
+ * @auto_zeroing_mux: set if ADC auto-zeroing algorithm is enabled
+ * @auto_zeroing_ref: set if ADC auto-Zeroing Reference Buffer Setting is enabled
+ */
+struct mcp356x_state {
+ const struct mcp356x_chip_info *chip_info;
+ struct iio_info mcp356x_info;
+ struct spi_device *spi;
+ struct regulator *vref;
+ unsigned short vref_mv;
+ struct mutex lock; /*lock to prevent concurrent reads/writes */
+ u8 dev_addr;
+ unsigned int oversampling;
+ unsigned int hwgain;
+ int calib_bias;
+ int calib_scale;
+ unsigned int current_boost_mode;
+ unsigned int current_bias_mode;
+ bool auto_zeroing_mux;
+ bool auto_zeroing_ref;
+};
+
+static inline u8 mcp356x_reg_write(u8 chip_addr, u8 reg)
+{
+ return ((chip_addr << 6) | (reg << 2) | MCP356X_WRT_CTRL);
+}
+
+static inline u8 mcp356x_reg_read(u8 chip_addr, u8 reg)
+{
+ return ((chip_addr << 6) | (reg << 2) | MCP356X_RD_CTRL);
+}
+
+static inline u8 mcp356x_reg_fast_cmd(u8 chip_addr, u8 cmd)
+{
+ return ((chip_addr << 6) | (cmd << 2));
+}
+
+static int mcp356x_read(struct mcp356x_state *adc, u8 reg, u32 *val, u8 len)
+{
+ int ret;
+ u8 tmp_reg;
+
+ tmp_reg = mcp356x_reg_read(adc->dev_addr, reg);
+
+ ret = spi_write_then_read(adc->spi, &tmp_reg, 1, val, len);
+
+ be32_to_cpus(val);
+ *val >>= ((4 - len) * 8);
+
+ return ret;
+}
+
+static int mcp356x_write(struct mcp356x_state *adc, u8 reg, u32 val, u8 len)
+{
+ val |= (mcp356x_reg_write(adc->dev_addr, reg) << (len * 8));
+ val <<= (3 - len) * 8;
+ cpu_to_be32s(&val);
+
+ return spi_write(adc->spi, &val, len + 1);
+}
+
+static int mcp356x_fast_cmd(struct mcp356x_state *adc, u8 fast_cmd)
+{
+ u8 val;
+
+ val = mcp356x_reg_fast_cmd(adc->dev_addr, fast_cmd);
+
+ return spi_write(adc->spi, &val, 1);
+}
+
+static int mcp356x_update(struct mcp356x_state *adc, u8 reg, u32 mask, u32 val,
+ u8 len)
+{
+ u32 tmp;
+ int ret;
+
+ ret = mcp356x_read(adc, reg, &tmp, len);
+
+ if (ret == 0) {
+ val &= mask;
+ val |= tmp & ~mask;
+ ret = mcp356x_write(adc, reg, val, len);
+ }
+
+ return ret;
+}
+
+/* Custom IIO Device Attributes */
+static int mcp356x_set_current_boost_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int mode)
+{
+ struct mcp356x_state *adc = iio_priv(indio_dev);
+ int ret;
+
+ dev_dbg(&indio_dev->dev, "%s: %d\n", __func__, mode);
+
+ mutex_lock(&adc->lock);
+ ret = mcp356x_update(adc, MCP356X_CONFIG2, MCP356X_BOOST_CURRENT_MASK,
+ mode, 1);
+
+ if (ret)
+ dev_err(&indio_dev->dev, "Failed to configure CONFIG2 register\n");
+ else
+ adc->current_boost_mode = mode;
+
+ mutex_unlock(&adc->lock);
+
+ return ret;
+}
+
+static int mcp356x_get_current_boost_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct mcp356x_state *adc = iio_priv(indio_dev);
+
+ return adc->current_boost_mode;
+}
+
+static int mcp356x_set_current_bias_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int mode)
+{
+ struct mcp356x_state *adc = iio_priv(indio_dev);
+ int ret;
+
+ dev_dbg(&indio_dev->dev, "%s: %d\n", __func__, mode);
+
+ mutex_lock(&adc->lock);
+ ret = mcp356x_update(adc, MCP356X_CONFIG0, MCP356X_CS_SEL_MASK, mode, 1);
+
+ if (ret)
+ dev_err(&indio_dev->dev, "Failed to configure CONFIG0 register\n");
+ else
+ adc->current_bias_mode = mode;
+
+ mutex_unlock(&adc->lock);
+
+ return ret;
+}
+
+static int mcp356x_get_current_bias_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct mcp356x_state *adc = iio_priv(indio_dev);
+
+ return adc->current_bias_mode;
+}
+
+static const struct iio_enum mcp356x_current_boost_mode_enum = {
+ .items = mcp356x_boost_current_avail,
+ .num_items = ARRAY_SIZE(mcp356x_boost_current_avail),
+ .set = mcp356x_set_current_boost_mode,
+ .get = mcp356x_get_current_boost_mode,
+};
+
+static const struct iio_enum mcp356x_current_bias_mode_enum = {
+ .items = mcp356x_current_bias_avail,
+ .num_items = ARRAY_SIZE(mcp356x_current_bias_avail),
+ .set = mcp356x_set_current_bias_mode,
+ .get = mcp356x_get_current_bias_mode,
+};
+
+static const struct iio_chan_spec_ext_info mcp356x_ext_info[] = {
+ IIO_ENUM("boost_current", IIO_SHARED_BY_ALL, &mcp356x_current_boost_mode_enum),
+ {
+ .name = "boost_current_available",
+ .shared = IIO_SHARED_BY_ALL,
+ .read = iio_enum_available_read,
+ .private = (uintptr_t)&mcp356x_current_boost_mode_enum,
+ },
+ IIO_ENUM("current_bias", IIO_SHARED_BY_ALL, &mcp356x_current_bias_mode_enum),
+ {
+ .name = "current_bias_available",
+ .shared = IIO_SHARED_BY_ALL,
+ .read = iio_enum_available_read,
+ .private = (uintptr_t)&mcp356x_current_bias_mode_enum,
+ },
+ {}
+};
+
+static ssize_t mcp356x_auto_zeroing_mux_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct mcp356x_state *adc = iio_priv(indio_dev);
+
+ return sysfs_emit(buf, "%d\n", adc->auto_zeroing_mux);
+}
+
+static ssize_t mcp356x_auto_zeroing_mux_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 mcp356x_state *adc = iio_priv(indio_dev);
+ bool auto_zero;
+ int ret;
+
+ ret = kstrtobool(buf, &auto_zero);
+ if (ret)
+ return ret;
+
+ mutex_lock(&adc->lock);
+ ret = mcp356x_update(adc, MCP356X_CONFIG2, MCP356X_AZ_MUX_MASK,
+ (u32)auto_zero, 1);
+
+ if (ret)
+ dev_err(&indio_dev->dev, "Failed to update CONFIG2 register\n");
+ else
+ adc->auto_zeroing_mux = auto_zero;
+
+ mutex_unlock(&adc->lock);
+
+ return ret ? ret : len;
+}
+
+static ssize_t mcp356x_auto_zeroing_ref_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct mcp356x_state *adc = iio_priv(indio_dev);
+
+ return sysfs_emit(buf, "%d\n", adc->auto_zeroing_ref);
+}
+
+static ssize_t mcp356x_auto_zeroing_ref_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 mcp356x_state *adc = iio_priv(indio_dev);
+ bool auto_zero;
+ int ret;
+
+ ret = kstrtobool(buf, &auto_zero);
+ if (ret)
+ return ret;
+
+ mutex_lock(&adc->lock);
+ ret = mcp356x_update(adc, MCP356X_CONFIG2, MCP356X_AZ_REF_MASK,
+ (u32)auto_zero, 1);
+
+ if (ret)
+ dev_err(&indio_dev->dev, "Failed to update CONFIG2 register\n");
+ else
+ adc->auto_zeroing_ref = auto_zero;
+
+ mutex_unlock(&adc->lock);
+
+ return ret ? ret : len;
+}
+
+#define MCP356X_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr)
+
+static IIO_DEVICE_ATTR(enable_auto_zeroing_ref, 0644,
+ mcp356x_auto_zeroing_ref_show,
+ mcp356x_auto_zeroing_ref_store, 0);
+
+static struct attribute *mcp356x_particular_attributes[] = {
+ MCP356X_DEV_ATTR(enable_auto_zeroing_ref),
+ NULL
+};
+
+static IIO_DEVICE_ATTR(enable_auto_zeroing_mux, 0644,
+ mcp356x_auto_zeroing_mux_show,
+ mcp356x_auto_zeroing_mux_store, 0);
+
+static struct attribute *mcp356x_shared_attributes[] = {
+ MCP356X_DEV_ATTR(enable_auto_zeroing_mux),
+ NULL,
+};
+
+static int mcp356x_prep_custom_attributes(struct mcp356x_state *adc,
+ struct iio_dev *indio_dev)
+{
+ int i;
+ struct attribute **mcp356x_custom_attr;
+ struct attribute_group *mcp356x_group;
+
+ mcp356x_group = devm_kzalloc(&adc->spi->dev, sizeof(*mcp356x_group), GFP_KERNEL);
+
+ if (!mcp356x_group)
+ return (-ENOMEM);
+
+ mcp356x_custom_attr = devm_kzalloc(&adc->spi->dev, (MCP356X_SHARED_DEVATTRS_COUNT +
+ MCP356X_PARTICULAR_DEVATTRS_COUNT + 1) * sizeof(struct attribute *),
+ GFP_KERNEL);
+
+ if (!mcp356x_custom_attr)
+ return (-ENOMEM);
+
+ for (i = 0; i < MCP356X_SHARED_DEVATTRS_COUNT; i++)
+ mcp356x_custom_attr[i] = mcp356x_shared_attributes[i];
+
+ if (adc->chip_info->has_vref) {
+ dev_dbg(&indio_dev->dev, "Setup custom attr for R variant\n");
+ for (i = 0; i < MCP356X_PARTICULAR_DEVATTRS_COUNT; i++)
+ mcp356x_custom_attr[MCP356X_SHARED_DEVATTRS_COUNT + i] =
+ mcp356x_particular_attributes[i];
+ }
+
+ mcp356x_group->attrs = mcp356x_custom_attr;
+ adc->mcp356x_info.attrs = mcp356x_group;
+
+ return 0;
+}
+
+#define MCP356X_V_CHANNEL(index, addr, depth) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .channel = (index), \
+ .address = (((addr) << 4) | MCP356X_AGND), \
+ .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_HARDWAREGAIN) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS) | \
+ BIT(IIO_CHAN_INFO_CALIBSCALE) | \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_CALIBSCALE) | \
+ BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS) | \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+ .ext_info = mcp356x_ext_info, \
+ .scan_index = 0, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = depth, \
+ .storagebits = 32, \
+ .endianness = IIO_BE, \
+ }, \
+}
+
+#define MCP356X_T_CHAN(depth) { \
+ .type = IIO_TEMP, \
+ .channel = 0, \
+ .address = ((MCP356X_TEMP_DIODE_P << 4) | MCP356X_TEMP_DIODE_M), \
+ .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_HARDWAREGAIN) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS) | \
+ BIT(IIO_CHAN_INFO_CALIBSCALE) | \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_CALIBSCALE) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS) | \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+ .ext_info = mcp356x_ext_info, \
+ .scan_index = 0, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = depth, \
+ .storagebits = 32, \
+ .endianness = IIO_BE, \
+ }, \
+}
+
+#define MCP356X_V_CHANNEL_DIFF(chan1, chan2, addr, depth) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .channel = (chan1), \
+ .channel2 = (chan2), \
+ .address = (addr), \
+ .differential = 1, \
+ .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_HARDWAREGAIN) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS) | \
+ BIT(IIO_CHAN_INFO_CALIBSCALE) | \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_CALIBSCALE) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS) | \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+ .ext_info = mcp356x_ext_info, \
+ .scan_index = 0, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = depth, \
+ .storagebits = 32, \
+ .endianness = IIO_BE, \
+ }, \
+}
+
+#define MCP3561_CHANNELS(depth) { \
+ MCP356X_V_CHANNEL(0, 0, depth), \
+ MCP356X_V_CHANNEL(1, 1, depth), \
+ MCP356X_V_CHANNEL_DIFF(0, 1, 0x01, depth), \
+ MCP356X_V_CHANNEL_DIFF(1, 0, 0x10, depth), \
+ MCP356X_T_CHAN(depth) \
+}
+
+#define MCP3562_CHANNELS(depth) { \
+ MCP356X_V_CHANNEL(0, 0, depth), \
+ MCP356X_V_CHANNEL(1, 1, depth), \
+ MCP356X_V_CHANNEL(2, 2, depth), \
+ MCP356X_V_CHANNEL(3, 3, depth), \
+ MCP356X_T_CHAN(depth), \
+ MCP356X_V_CHANNEL_DIFF(0, 1, 0x01, depth), \
+ MCP356X_V_CHANNEL_DIFF(1, 0, 0x10, depth), \
+ MCP356X_V_CHANNEL_DIFF(0, 2, 0x02, depth), \
+ MCP356X_V_CHANNEL_DIFF(0, 3, 0x03, depth), \
+ MCP356X_V_CHANNEL_DIFF(1, 2, 0x12, depth), \
+ MCP356X_V_CHANNEL_DIFF(1, 3, 0x13, depth), \
+ MCP356X_V_CHANNEL_DIFF(2, 3, 0x23, depth), \
+ MCP356X_V_CHANNEL_DIFF(2, 0, 0x20, depth), \
+ MCP356X_V_CHANNEL_DIFF(3, 0, 0x30, depth), \
+ MCP356X_V_CHANNEL_DIFF(2, 1, 0x21, depth), \
+ MCP356X_V_CHANNEL_DIFF(3, 1, 0x31, depth), \
+ MCP356X_V_CHANNEL_DIFF(3, 2, 0x32, depth), \
+}
+
+#define MCP3564_CHANNELS(depth) { \
+ MCP356X_V_CHANNEL(0, 0, depth), \
+ MCP356X_V_CHANNEL(1, 1, depth), \
+ MCP356X_V_CHANNEL(2, 2, depth), \
+ MCP356X_V_CHANNEL(3, 3, depth), \
+ MCP356X_V_CHANNEL(4, 4, depth), \
+ MCP356X_V_CHANNEL(5, 5, depth), \
+ MCP356X_V_CHANNEL(6, 6, depth), \
+ MCP356X_V_CHANNEL(7, 7, depth), \
+ MCP356X_T_CHAN(depth), \
+ MCP356X_V_CHANNEL_DIFF(0, 1, 0x01, depth), \
+ MCP356X_V_CHANNEL_DIFF(1, 0, 0x10, depth), \
+ MCP356X_V_CHANNEL_DIFF(0, 2, 0x02, depth), \
+ MCP356X_V_CHANNEL_DIFF(0, 3, 0x03, depth), \
+ MCP356X_V_CHANNEL_DIFF(1, 2, 0x12, depth), \
+ MCP356X_V_CHANNEL_DIFF(1, 3, 0x13, depth), \
+ MCP356X_V_CHANNEL_DIFF(2, 3, 0x23, depth), \
+ MCP356X_V_CHANNEL_DIFF(2, 0, 0x20, depth), \
+ MCP356X_V_CHANNEL_DIFF(3, 0, 0x30, depth), \
+ MCP356X_V_CHANNEL_DIFF(2, 1, 0x21, depth), \
+ MCP356X_V_CHANNEL_DIFF(3, 1, 0x31, depth), \
+ MCP356X_V_CHANNEL_DIFF(3, 2, 0x32, depth), \
+ MCP356X_V_CHANNEL_DIFF(0, 4, 0x04, depth), \
+ MCP356X_V_CHANNEL_DIFF(0, 5, 0x05, depth), \
+ MCP356X_V_CHANNEL_DIFF(0, 6, 0x06, depth), \
+ MCP356X_V_CHANNEL_DIFF(0, 7, 0x07, depth), \
+ MCP356X_V_CHANNEL_DIFF(1, 4, 0x14, depth), \
+ MCP356X_V_CHANNEL_DIFF(1, 5, 0x15, depth), \
+ MCP356X_V_CHANNEL_DIFF(1, 6, 0x16, depth), \
+ MCP356X_V_CHANNEL_DIFF(1, 7, 0x17, depth), \
+ MCP356X_V_CHANNEL_DIFF(2, 4, 0x24, depth), \
+ MCP356X_V_CHANNEL_DIFF(2, 5, 0x25, depth), \
+ MCP356X_V_CHANNEL_DIFF(2, 6, 0x26, depth), \
+ MCP356X_V_CHANNEL_DIFF(2, 7, 0x27, depth), \
+ MCP356X_V_CHANNEL_DIFF(3, 4, 0x34, depth), \
+ MCP356X_V_CHANNEL_DIFF(3, 5, 0x35, depth), \
+ MCP356X_V_CHANNEL_DIFF(3, 6, 0x36, depth), \
+ MCP356X_V_CHANNEL_DIFF(3, 7, 0x37, depth), \
+ MCP356X_V_CHANNEL_DIFF(4, 5, 0x45, depth), \
+ MCP356X_V_CHANNEL_DIFF(4, 6, 0x46, depth), \
+ MCP356X_V_CHANNEL_DIFF(4, 7, 0x47, depth), \
+ MCP356X_V_CHANNEL_DIFF(5, 6, 0x56, depth), \
+ MCP356X_V_CHANNEL_DIFF(5, 7, 0x57, depth), \
+ MCP356X_V_CHANNEL_DIFF(6, 7, 0x67, depth), \
+ MCP356X_V_CHANNEL_DIFF(4, 0, 0x40, depth), \
+ MCP356X_V_CHANNEL_DIFF(5, 0, 0x50, depth), \
+ MCP356X_V_CHANNEL_DIFF(6, 0, 0x60, depth), \
+ MCP356X_V_CHANNEL_DIFF(7, 0, 0x70, depth), \
+ MCP356X_V_CHANNEL_DIFF(4, 1, 0x41, depth), \
+ MCP356X_V_CHANNEL_DIFF(5, 1, 0x51, depth), \
+ MCP356X_V_CHANNEL_DIFF(6, 1, 0x61, depth), \
+ MCP356X_V_CHANNEL_DIFF(7, 1, 0x71, depth), \
+ MCP356X_V_CHANNEL_DIFF(4, 2, 0x42, depth), \
+ MCP356X_V_CHANNEL_DIFF(5, 2, 0x52, depth), \
+ MCP356X_V_CHANNEL_DIFF(6, 2, 0x62, depth), \
+ MCP356X_V_CHANNEL_DIFF(7, 2, 0x72, depth), \
+ MCP356X_V_CHANNEL_DIFF(4, 3, 0x43, depth), \
+ MCP356X_V_CHANNEL_DIFF(5, 3, 0x53, depth), \
+ MCP356X_V_CHANNEL_DIFF(6, 3, 0x63, depth), \
+ MCP356X_V_CHANNEL_DIFF(7, 3, 0x73, depth), \
+ MCP356X_V_CHANNEL_DIFF(5, 4, 0x54, depth), \
+ MCP356X_V_CHANNEL_DIFF(6, 4, 0x64, depth), \
+ MCP356X_V_CHANNEL_DIFF(7, 4, 0x74, depth), \
+ MCP356X_V_CHANNEL_DIFF(6, 5, 0x65, depth), \
+ MCP356X_V_CHANNEL_DIFF(7, 5, 0x75, depth), \
+ MCP356X_V_CHANNEL_DIFF(7, 6, 0x76, depth) \
+}
+
+static const struct iio_chan_spec mcp3461_channels[] = MCP3561_CHANNELS(16);
+static const struct iio_chan_spec mcp3462_channels[] = MCP3562_CHANNELS(16);
+static const struct iio_chan_spec mcp3464_channels[] = MCP3564_CHANNELS(16);
+static const struct iio_chan_spec mcp3561_channels[] = MCP3561_CHANNELS(24);
+static const struct iio_chan_spec mcp3562_channels[] = MCP3562_CHANNELS(24);
+static const struct iio_chan_spec mcp3564_channels[] = MCP3564_CHANNELS(24);
+
+static const struct mcp356x_chip_info mcp356x_chip_infos_tbl[] = {
+ [mcp3461] = {
+ .channels = mcp3461_channels,
+ .num_channels = ARRAY_SIZE(mcp3461_channels),
+ .int_vref_uv = 0,
+ .has_vref = false
+ },
+ [mcp3462] = {
+ .channels = mcp3462_channels,
+ .num_channels = ARRAY_SIZE(mcp3462_channels),
+ .int_vref_uv = 0,
+ .has_vref = false
+ },
+ [mcp3464] = {
+ .channels = mcp3464_channels,
+ .num_channels = ARRAY_SIZE(mcp3464_channels),
+ .int_vref_uv = 0,
+ .has_vref = false
+ },
+ [mcp3461r] = {
+ .channels = mcp3461_channels,
+ .num_channels = ARRAY_SIZE(mcp3461_channels),
+ .int_vref_uv = MCP356XR_INT_VREF_MV,
+ .has_vref = true
+ },
+ [mcp3462r] = {
+ .channels = mcp3462_channels,
+ .num_channels = ARRAY_SIZE(mcp3462_channels),
+ .int_vref_uv = MCP356XR_INT_VREF_MV,
+ .has_vref = true
+ },
+ [mcp3464r] = {
+ .channels = mcp3464_channels,
+ .num_channels = ARRAY_SIZE(mcp3464_channels),
+ .int_vref_uv = MCP356XR_INT_VREF_MV,
+ .has_vref = true
+ },
+ [mcp3561] = {
+ .channels = mcp3561_channels,
+ .num_channels = ARRAY_SIZE(mcp3561_channels),
+ .int_vref_uv = 0,
+ .has_vref = false
+ },
+ [mcp3562] = {
+ .channels = mcp3562_channels,
+ .num_channels = ARRAY_SIZE(mcp3562_channels),
+ .int_vref_uv = 0,
+ .has_vref = false
+ },
+ [mcp3564] = {
+ .channels = mcp3564_channels,
+ .num_channels = ARRAY_SIZE(mcp3564_channels),
+ .int_vref_uv = 0,
+ .has_vref = false
+ },
+ [mcp3561r] = {
+ .channels = mcp3561_channels,
+ .num_channels = ARRAY_SIZE(mcp3561_channels),
+ .int_vref_uv = MCP356XR_INT_VREF_MV,
+ .has_vref = true
+ },
+ [mcp3562r] = {
+ .channels = mcp3562_channels,
+ .num_channels = ARRAY_SIZE(mcp3562_channels),
+ .int_vref_uv = MCP356XR_INT_VREF_MV,
+ .has_vref = true
+ },
+ [mcp3564r] = {
+ .channels = mcp3564_channels,
+ .num_channels = ARRAY_SIZE(mcp3564_channels),
+ .int_vref_uv = MCP356XR_INT_VREF_MV,
+ .has_vref = true
+ },
+};
+
+static int mcp356x_read_single_value(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel,
+ int *val)
+{
+ struct mcp356x_state *adc = iio_priv(indio_dev);
+ int ret, tmp, ret_read = 0;
+
+ /* Configure MUX register with the requested channel */
+ ret = mcp356x_write(adc, MCP356X_MUX, channel->address, 1);
+ if (ret) {
+ dev_err(&indio_dev->dev, "Failed to configure MUX register\n");
+ return ret;
+ }
+
+ /* ADC Conversion starts by writing ADC_MODE[1:0] = 11 to CONFIG0[1:0] = */
+ ret = mcp356x_update(adc, MCP356X_CONFIG0, MCP356X_ADC_MODE_MASK,
+ MCP356X_ADC_CONVERSION_MODE, 1);
+ if (ret) {
+ dev_err(&indio_dev->dev,
+ "Failed to configure CONFIG0 register\n");
+ return ret;
+ }
+
+ /*
+ * Check if the conversion is ready. If not, wait a little bit, and
+ * in case of timeout exit with an error.
+ */
+
+ ret = read_poll_timeout(mcp356x_read, ret_read,
+ ret_read || !(tmp & MCP356X_DATA_READY_MASK),
+ 1000, MCP356X_DATA_READY_TIMEOUT_MS * 1000, true,
+ adc, MCP356X_IRQ, &tmp, 1);
+
+ /* failed to read status register */
+ if (ret_read)
+ return ret;
+
+ if (ret)
+ return -ETIMEDOUT;
+
+ if (tmp & MCP356X_DATA_READY_MASK)
+ /* failing to finish conversion */
+ return -EBUSY;
+
+ ret = mcp356x_read(adc, MCP356X_ADCDATA, &tmp, 4);
+ if (ret)
+ return ret;
+
+ *val = tmp;
+
+ return ret;
+}
+
+static int mcp356x_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel,
+ const int **vals, int *type,
+ int *length, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ *type = IIO_VAL_INT;
+ *vals = mcp356x_oversampling_avail;
+ *length = ARRAY_SIZE(mcp356x_oversampling_avail);
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ *type = IIO_VAL_FRACTIONAL;
+ *length = ARRAY_SIZE(mcp356x_hwgain_frac);
+ *vals = mcp356x_hwgain_frac;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ *vals = mcp356x_calib_bias;
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_RANGE;
+ case IIO_CHAN_INFO_CALIBSCALE:
+ *vals = mcp356x_calib_scale;
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_RANGE;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mcp356x_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel,
+ int *val, int *val2, long mask)
+{
+ struct mcp356x_state *adc = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&adc->lock);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = mcp356x_read_single_value(indio_dev, channel, val);
+ if (ret)
+ ret = -EINVAL;
+ else
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_CHAN_INFO_SCALE:
+ *val = adc->vref_mv;
+ *val2 = channel->scan_type.realbits - 1;
+ ret = IIO_VAL_FRACTIONAL_LOG2;
+ break;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ *val = mcp356x_oversampling_avail[adc->oversampling];
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ *val = mcp356x_hwgain_frac[2 * adc->hwgain];
+ *val2 = mcp356x_hwgain_frac[(2 * adc->hwgain) + 1];
+ ret = IIO_VAL_FRACTIONAL;
+ break;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ *val = adc->calib_bias;
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_CHAN_INFO_CALIBSCALE:
+ *val = adc->calib_scale;
+ ret = IIO_VAL_INT;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ mutex_unlock(&adc->lock);
+
+ return ret;
+}
+
+static int mcp356x_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int val,
+ int val2, long mask)
+{
+ struct mcp356x_state *adc = iio_priv(indio_dev);
+ int tmp;
+ int ret = -EINVAL;
+
+ mutex_lock(&adc->lock);
+ switch (mask) {
+ case IIO_CHAN_INFO_CALIBBIAS:
+ if (val < mcp356x_calib_bias[0] && val > mcp356x_calib_bias[2])
+ goto out;
+
+ adc->calib_bias = val;
+ ret = mcp356x_write(adc, MCP356X_OFFSETCAL, val, 3);
+ break;
+ case IIO_CHAN_INFO_CALIBSCALE:
+ if (val < mcp356x_calib_bias[0] && val > mcp356x_calib_bias[2])
+ goto out;
+
+ adc->calib_scale = val;
+ ret = mcp356x_write(adc, MCP356X_GAINCAL, val, 3);
+ break;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ if (val < 0)
+ goto out;
+
+ adc->oversampling = find_closest(val, mcp356x_oversampling_avail,
+ ARRAY_SIZE(mcp356x_oversampling_avail));
+
+ dev_dbg(&adc->spi->dev,
+ "IIO_CHAN_INFO_OVERSAMPLING_RATIO index %d\n",
+ adc->oversampling);
+
+ ret = mcp356x_update(adc, MCP356X_CONFIG1, MCP356X_OVERSAMPLING_RATIO_MASK,
+ (adc->oversampling << MCP356X_OVERSAMPLING_RATIO_SHIFT),
+ 1);
+ if (ret)
+ dev_err(&indio_dev->dev,
+ "Failed to configure CONFIG1 register\n");
+
+ break;
+ case IIO_CHAN_INFO_HARDWAREGAIN:
+ /*
+ * calculate gain from read values.
+ * avoid using fractional numbers so
+ * multiply the value with 1000. In case of x1/3 gain
+ * the tmp will be 300
+ */
+ tmp = ((val * 1000000) + val2) / 1000;
+ if (tmp < 1 || tmp > MAX_HWGAIN)
+ goto out;
+
+ adc->hwgain = find_closest(tmp, mcp356x_hwgain,
+ ARRAY_SIZE(mcp356x_hwgain));
+
+ dev_dbg(&adc->spi->dev,
+ "IIO_CHAN_INFO_HARDWAREGAIN Gain:%d; index %d\n",
+ tmp, adc->hwgain);
+
+ /* Update GAIN in CONFIG2[5:3] -> GAIN[2:0]*/
+ ret = mcp356x_update(adc, MCP356X_CONFIG2, MCP356X_HARDWARE_GAIN_MASK,
+ (adc->hwgain << MCP356X_HARDWARE_GAIN_SHIFT), 1);
+ if (ret)
+ dev_err(&indio_dev->dev,
+ "Failed to configure CONFIG0 register\n");
+ break;
+ }
+
+out:
+ mutex_unlock(&adc->lock);
+
+ return ret;
+}
+
+static int mcp356x_config(struct mcp356x_state *adc)
+{
+ int ret = 0;
+ unsigned int tmp;
+
+ dev_dbg(&adc->spi->dev, "%s: Start config...\n", __func__);
+
+ /*
+ * The address is set on a per-device basis by fuses in the factory,
+ * configured on request. If not requested, the fuses are set for 0x1.
+ * The device address is part of the device markings to avoid
+ * potential confusion. This address is coded on two bits, so four possible
+ * addresses are available when multiple devices are present on the same
+ * SPI bus with only one Chip Select line for all devices.
+ */
+ device_property_read_u32(&adc->spi->dev, "microchip,hw-device-address", &tmp);
+
+ if (tmp > 3) {
+ dev_err_probe(&adc->spi->dev, tmp,
+ "invalid device address. Must be in range 0-3.\n");
+ return -EINVAL;
+ }
+
+ adc->dev_addr = 0xff & tmp;
+
+ dev_dbg(&adc->spi->dev, "use device address %i\n", adc->dev_addr);
+
+ ret = mcp356x_read(adc, MCP356X_RESERVED_E, &tmp, 2);
+
+ if (ret == 0) {
+ switch (tmp & MCP356X_HW_ID_MASK) {
+ case MCP3461_HW_ID:
+ dev_dbg(&adc->spi->dev, "Found MCP3461 chip\n");
+ break;
+ case MCP3462_HW_ID:
+ dev_dbg(&adc->spi->dev, "Found MCP3462 chip\n");
+ break;
+ case MCP3464_HW_ID:
+ dev_dbg(&adc->spi->dev, "Found MCP3464 chip\n");
+ break;
+ case MCP3561_HW_ID:
+ dev_dbg(&adc->spi->dev, "Found MCP3561 chip\n");
+ break;
+ case MCP3562_HW_ID:
+ dev_dbg(&adc->spi->dev, "Found MCP3562 chip\n");
+ break;
+ case MCP3564_HW_ID:
+ dev_dbg(&adc->spi->dev, "Found MCP3564 chip\n");
+ break;
+ default:
+ dev_err_probe(&adc->spi->dev, tmp,
+ "Unknown chip found\n");
+ return -EINVAL;
+ }
+ } else {
+ return ret;
+ }
+
+ /* Command sequence that ensures a recovery with
+ * the desired settings in any cases of loss-of-power scenario.
+ */
+
+ /* Write LOCK register to 0xA5 (Write Access Password)
+ * Write access is allowed on the full register map.
+ */
+ ret = mcp356x_write(adc, MCP356X_LOCK, 0x000000A5, 1);
+ if (ret)
+ return ret;
+
+ /* Write IRQ register to 0x03 */
+ /* IRQ --> IRQ Mode = Hi-Z IRQ Output --> (0b00000011).
+ * IRQ = 0x00000003
+ */
+ ret = mcp356x_write(adc, MCP356X_IRQ, 0x00000003, 1);
+ if (ret)
+ return ret;
+
+ /* Device Full Reset Fast Command */
+ ret = mcp356x_fast_cmd(adc, MCP356X_FULL_RESET_CMD);
+
+ /* wait 1ms for the chip to restart after a full reset */
+ mdelay(1);
+
+ /* Reconfigure the ADC chip */
+
+ /* GAINCAL --> Disabled.
+ * Default value is GAINCAL = 0x00800000; which provides a gain of 1x
+ */
+ ret = mcp356x_write(adc, MCP356X_GAINCAL, 0x00800000, 3);
+ if (ret)
+ return ret;
+
+ adc->calib_scale = 0x00800000;
+
+ /* OFFSETCAL --> 0 Counts of Offset Cancellation
+ * (Measured offset is negative).
+ * OFFSETCAL = 0x0
+ */
+ ret = mcp356x_write(adc, MCP356X_OFFSETCAL, 0x00000000, 3);
+ if (ret)
+ return ret;
+
+ /* TIMER --> Disabled.
+ * TIMER = 0x00000000
+ */
+ ret = mcp356x_write(adc, MCP356X_TIMER, 0x00000000, 3);
+ if (ret)
+ return ret;
+
+ /* SCAN --> Disabled.
+ * SCAN = 0x00000000
+ */
+ ret = mcp356x_write(adc, MCP356X_SCAN, 0x00000000, 3);
+ if (ret)
+ return ret;
+
+ /* MUX --> VIN+ = CH0, VIN- = CH1 --> (0b00000001).
+ * MUX = 0x00000001
+ */
+ ret = mcp356x_write(adc, MCP356X_MUX, 0x00000001, 1);
+ if (ret)
+ return ret;
+
+ /* IRQ --> IRQ Mode = Hi-Z IRQ Output --> (0b00000011).
+ * IRQ = 0x00000003
+ */
+ ret = mcp356x_write(adc, MCP356X_IRQ, 0x00000003, 1);
+ if (ret)
+ return ret;
+
+ /* CONFIG3
+ * Conv. Mod = One-Shot/Standby,
+ * FORMAT = 32-bit (right justified data): SGN extension + ADC data,
+ * CRC_FORMAT = 16b, CRC-COM = Disabled,
+ * OFFSETCAL = Enabled, GAINCAL = Enabled --> (10100011).
+ * CONFIG3 = 0x000000A3
+ *
+ */
+ ret = mcp356x_write(adc, MCP356X_CONFIG3, 0x000000A3, 1);
+ if (ret)
+ return ret;
+
+ /* CONFIG2 --> BOOST = 1x, GAIN = 1x, AZ_MUX = 1 --> (0b10001101).
+ * CONFIG2 = 0x0000008D
+ */
+ ret = mcp356x_write(adc, MCP356X_CONFIG2, 0x0000008D, 1);
+ if (ret)
+ return ret;
+
+ adc->hwgain = 0x01;
+ adc->auto_zeroing_mux = true;
+ adc->auto_zeroing_ref = false;
+ adc->current_boost_mode = MCP356X_BOOST_CURRENT_x1_00;
+
+ /* CONFIG1 --> AMCLK = MCLK, OSR = 98304 --> (0b00111100).
+ * CONFIG1 = 0x0000003C
+ */
+ ret = mcp356x_write(adc, MCP356X_CONFIG1, 0x0000003C, 1);
+ if (ret)
+ return ret;
+
+ adc->oversampling = 0x0F;
+
+ if (!adc->vref) {
+ /* CONFIG0 --> VREF_SEL = Internal Voltage Reference 2.4v
+ * CLK_SEL = INTOSC w/o CLKOUT, CS_SEL = No Bias,
+ * ADC_MODE = Standby Mode --> (0b11100010).
+ * CONFIG0 = 0x000000E2
+ */
+ ret = mcp356x_write(adc, MCP356X_CONFIG0, 0x000000E2, 1);
+
+ dev_dbg(&adc->spi->dev, "%s: Using internal Vref\n",
+ __func__);
+ adc->vref_mv = MCP356XR_INT_VREF_MV;
+
+ } else {
+ /* CONFIG0 --> CLK_SEL = INTOSC w/o CLKOUT, CS_SEL = No Bias,
+ * ADC_MODE = Standby Mode --> (0b01100010).
+ * CONFIG0 = 0x000000E2
+ */
+ ret = mcp356x_write(adc, MCP356X_CONFIG0, 0x00000062, 1);
+ }
+ adc->current_bias_mode = MCP356X_CS_SEL_0_0_uA;
+
+ return ret;
+}
+
+static int mcp356x_probe(struct spi_device *spi)
+{
+ int ret, device_index;
+ struct iio_dev *indio_dev;
+ struct mcp356x_state *adc;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc));
+ if (!indio_dev) {
+ dev_err_probe(&indio_dev->dev, PTR_ERR(indio_dev),
+ "Can't allocate iio device\n");
+ return -ENOMEM;
+ }
+
+ adc = iio_priv(indio_dev);
+ adc->spi = spi;
+
+ dev_dbg(&adc->spi->dev, "%s: probe(spi = 0x%p)\n", __func__, spi);
+
+ adc->vref = devm_regulator_get_optional(&adc->spi->dev, "vref");
+ if (IS_ERR(adc->vref)) {
+ if (PTR_ERR(adc->vref) == -ENODEV) {
+ adc->vref = NULL;
+ dev_dbg(&adc->spi->dev, "%s: Using internal Vref\n",
+ __func__);
+ } else {
+ dev_err_probe(&adc->spi->dev, PTR_ERR(adc->vref),
+ "failed to get regulator\n");
+ return PTR_ERR(adc->vref);
+ }
+ } else {
+ ret = regulator_enable(adc->vref);
+ if (ret)
+ return ret;
+
+ dev_dbg(&adc->spi->dev, "%s: Using External Vref\n",
+ __func__);
+
+ ret = regulator_get_voltage(adc->vref);
+ if (ret < 0) {
+ dev_err_probe(&adc->spi->dev, ret,
+ "Failed to read vref regulator\n");
+ goto error_disable_reg;
+ }
+
+ adc->vref_mv = ret / 1000;
+ }
+
+ spi_set_drvdata(spi, indio_dev);
+ device_index = spi_get_device_id(spi)->driver_data;
+ adc->chip_info = &mcp356x_chip_infos_tbl[device_index];
+
+ adc->mcp356x_info.read_raw = mcp356x_read_raw;
+ adc->mcp356x_info.write_raw = mcp356x_write_raw;
+ adc->mcp356x_info.read_avail = mcp356x_read_avail;
+
+ ret = mcp356x_prep_custom_attributes(adc, indio_dev);
+ if (ret) {
+ dev_err_probe(&adc->spi->dev, ret,
+ "Can't configure custom attributes for MCP356X device\n");
+ goto error_disable_reg;
+ }
+
+ indio_dev->name = spi_get_device_id(spi)->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &adc->mcp356x_info;
+
+ indio_dev->channels = adc->chip_info->channels;
+ indio_dev->num_channels = adc->chip_info->num_channels;
+ indio_dev->masklength = adc->chip_info->num_channels - 1;
+
+ /* initialize the chip access mutex */
+ mutex_init(&adc->lock);
+
+ /* Do any chip specific initialization, e.g:
+ * read/write some registers
+ * enable/disable certain channels
+ * change the sampling rate to the requested value
+ */
+ ret = mcp356x_config(adc);
+ if (ret) {
+ dev_err_probe(&adc->spi->dev, ret,
+ "Can't configure MCP356X device\n");
+ goto error_disable_reg;
+ }
+
+ dev_dbg(&adc->spi->dev, "%s: Vref (mV): %d\n", __func__, adc->vref_mv);
+
+ ret = devm_iio_device_register(&spi->dev, indio_dev);
+ if (ret) {
+ dev_err_probe(&adc->spi->dev, ret,
+ "Can't register IIO device\n");
+ goto error_disable_reg;
+ }
+
+ return 0;
+
+error_disable_reg:
+ if (adc->vref)
+ regulator_disable(adc->vref);
+
+ return ret;
+}
+
+static void mcp356x_remove(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev = spi_get_drvdata(spi);
+ struct mcp356x_state *adc = iio_priv(indio_dev);
+
+ if (adc->vref)
+ regulator_disable(adc->vref);
+}
+
+static const struct of_device_id mcp356x_dt_ids[] = {
+ { .compatible = "microchip,mcp3461" },
+ { .compatible = "microchip,mcp3462" },
+ { .compatible = "microchip,mcp3464" },
+ { .compatible = "microchip,mcp3461r" },
+ { .compatible = "microchip,mcp3462r" },
+ { .compatible = "microchip,mcp3464r" },
+ { .compatible = "microchip,mcp3561" },
+ { .compatible = "microchip,mcp3562" },
+ { .compatible = "microchip,mcp3564" },
+ { .compatible = "microchip,mcp3561r" },
+ { .compatible = "microchip,mcp3562r" },
+ { .compatible = "microchip,mcp3564r" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mcp356x_dt_ids);
+
+static const struct spi_device_id mcp356x_id[] = {
+ { "mcp3461", mcp3461 },
+ { "mcp3462", mcp3462 },
+ { "mcp3464", mcp3464 },
+ { "mcp3461r", mcp3461r },
+ { "mcp3462r", mcp3462r },
+ { "mcp3464r", mcp3464r },
+ { "mcp3561", mcp3561 },
+ { "mcp3562", mcp3562 },
+ { "mcp3564", mcp3564 },
+ { "mcp3561r", mcp3561r },
+ { "mcp3562r", mcp3562r },
+ { "mcp3564r", mcp3564r },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, mcp356x_id);
+
+static struct spi_driver mcp356x_driver = {
+ .driver = {
+ .name = "mcp3564",
+ .of_match_table = mcp356x_dt_ids,
+ },
+ .probe = mcp356x_probe,
+ .remove = mcp356x_remove,
+ .id_table = mcp356x_id,
+};
+
+module_spi_driver(mcp356x_driver);
+
+MODULE_AUTHOR("Marius Cristea <marius.cristea@...rochip.com>");
+MODULE_DESCRIPTION("Microchip MCP346x/MCP346xR and MCP356x/MCP346xR ADCs");
+MODULE_LICENSE("GPL v2");
--
2.34.1
Powered by blists - more mailing lists