From 51e3b82d70fd95e72035cfc393b50a9b5410cd35 Mon Sep 17 00:00:00 2001 From: vauban353 Date: Fri, 16 Jun 2023 16:05:01 +0100 Subject: [PATCH] ADC: Add Microchip MCP356X driver. --- drivers/iio/adc/Kconfig | 10 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/mcp356x.c | 1396 +++++++++++++++++++++++++++++++++++++ 3 files changed, 1407 insertions(+) create mode 100644 drivers/iio/adc/mcp356x.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 48763d6a89a4..6c6b62a7f3e6 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -741,6 +741,16 @@ config MCP3911 This driver can also be built as a module. If so, the module will be called mcp3911. +config MCP356X + tristate "Microchip Technology MCP356X driver" + depends on SPI + help + Say yes here to build support for Microchip Technology's MCP356X + analog to digital converter. + + This driver can also be built as a module. If so, the module will be + called mcp356x. + config MEDIATEK_MT6360_ADC tristate "Mediatek MT6360 ADC driver" depends on MFD_MT6360 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index d5ca372050cb..5b3f027ac74c 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -68,6 +68,7 @@ obj-$(CONFIG_MAX9611) += max9611.o obj-$(CONFIG_MCP320X) += mcp320x.o obj-$(CONFIG_MCP3422) += mcp3422.o obj-$(CONFIG_MCP3911) += mcp3911.o +obj-$(CONFIG_MCP356X) += mcp356x.o obj-$(CONFIG_MEDIATEK_MT6360_ADC) += mt6360-adc.o obj-$(CONFIG_MEDIATEK_MT6577_AUXADC) += mt6577_auxadc.o obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o diff --git a/drivers/iio/adc/mcp356x.c b/drivers/iio/adc/mcp356x.c new file mode 100644 index 000000000000..22d59413d342 --- /dev/null +++ b/drivers/iio/adc/mcp356x.c @@ -0,0 +1,1396 @@ +// 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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 "); +MODULE_DESCRIPTION("Microchip MCP346x/MCP346xR and MCP356x/MCP346xR ADCs"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.1.2"); -- 2.25.1