root/drivers/iio/adc/mcp3564.c
// 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@microchip.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/bitfield.h>
#include <linux/iopoll.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
#include <linux/units.h>
#include <linux/util_macros.h>

#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>

#define MCP3564_ADCDATA_REG             0x00

#define MCP3564_CONFIG0_REG             0x01
#define MCP3564_CONFIG0_ADC_MODE_MASK           GENMASK(1, 0)
/* Current Source/Sink Selection Bits for Sensor Bias */
#define MCP3564_CONFIG0_CS_SEL_MASK             GENMASK(3, 2)
/* Internal clock is selected and AMCLK is present on the analog master clock output pin */
#define MCP3564_CONFIG0_USE_INT_CLK_OUTPUT_EN   0x03
/* Internal clock is selected and no clock output is present on the CLK pin */
#define MCP3564_CONFIG0_USE_INT_CLK             0x02
/* External digital clock */
#define MCP3564_CONFIG0_USE_EXT_CLK             0x01
/* External digital clock (default) */
#define MCP3564_CONFIG0_USE_EXT_CLK_DEFAULT     0x00
#define MCP3564_CONFIG0_CLK_SEL_MASK            GENMASK(5, 4)
#define MCP3456_CONFIG0_BIT6_DEFAULT            BIT(6)
#define MCP3456_CONFIG0_VREF_MASK               BIT(7)

#define MCP3564_CONFIG1_REG             0x02
#define MCP3564_CONFIG1_OVERSPL_RATIO_MASK      GENMASK(5, 2)

#define MCP3564_CONFIG2_REG             0x03
#define MCP3564_CONFIG2_AZ_REF_MASK             BIT(1)
#define MCP3564_CONFIG2_AZ_MUX_MASK             BIT(2)

#define MCP3564_CONFIG2_HARDWARE_GAIN_MASK      GENMASK(5, 3)
#define MCP3564_DEFAULT_HARDWARE_GAIN           0x01
#define MCP3564_CONFIG2_BOOST_CURRENT_MASK      GENMASK(7, 6)

#define MCP3564_CONFIG3_REG             0x04
#define MCP3464_CONFIG3_EN_GAINCAL_MASK         BIT(0)
#define MCP3464_CONFIG3_EN_OFFCAL_MASK          BIT(1)
#define MCP3464_CONFIG3_EN_CRCCOM_MASK          BIT(2)
#define MCP3464_CONFIG3_CRC_FORMAT_MASK         BIT(3)
/*
 * ADC Output Data Format 32-bit (25-bit right justified data + Channel ID):
 *                CHID[3:0] + SGN extension (4 bits) + 24-bit ADC data.
 *        It allows overrange with the SGN extension.
 */
#define MCP3464_CONFIG3_DATA_FMT_32B_WITH_CH_ID         3
/*
 * ADC Output Data Format 32-bit (25-bit right justified data):
 *                SGN extension (8-bit) + 24-bit ADC data.
 *        It allows overrange with the SGN extension.
 */
#define MCP3464_CONFIG3_DATA_FMT_32B_SGN_EXT            2
/*
 * ADC Output Data Format 32-bit (24-bit left justified data):
 *                24-bit ADC data + 0x00 (8-bit).
 *        It does not allow overrange (ADC code locked to 0xFFFFFF or 0x800000).
 */
#define MCP3464_CONFIG3_DATA_FMT_32B_LEFT_JUSTIFIED     1
/*
 * ADC Output Data Format 24-bit (default ADC coding):
 *                24-bit ADC data.
 *        It does not allow overrange (ADC code locked to 0xFFFFFF or 0x800000).
 */
#define MCP3464_CONFIG3_DATA_FMT_24B                    0
#define MCP3464_CONFIG3_DATA_FORMAT_MASK        GENMASK(5, 4)

/* Continuous Conversion mode or continuous conversion cycle in SCAN mode. */
#define MCP3464_CONFIG3_CONV_MODE_CONTINUOUS            3
/*
 * One-shot conversion or one-shot cycle in SCAN mode. It sets ADC_MODE[1:0] to ‘10’
 * (standby) at the end of the conversion or at the end of the conversion cycle in SCAN mode.
 */
#define MCP3464_CONFIG3_CONV_MODE_ONE_SHOT_STANDBY      2
/*
 * One-shot conversion or one-shot cycle in SCAN mode. It sets ADC_MODE[1:0] to ‘0x’ (ADC
 * Shutdown) at the end of the conversion or at the end of the conversion cycle in SCAN
 * mode (default).
 */
#define MCP3464_CONFIG3_CONV_MODE_ONE_SHOT_SHUTDOWN     0
#define MCP3464_CONFIG3_CONV_MODE_MASK          GENMASK(7, 6)

#define MCP3564_IRQ_REG                 0x05
#define MCP3464_EN_STP_MASK                     BIT(0)
#define MCP3464_EN_FASTCMD_MASK                 BIT(1)
#define MCP3464_IRQ_MODE_0_MASK                 BIT(2)
#define MCP3464_IRQ_MODE_1_MASK                 BIT(3)
#define MCP3564_POR_STATUS_MASK                 BIT(4)
#define MCP3564_CRCCFG_STATUS_MASK              BIT(5)
#define MCP3564_DATA_READY_MASK                 BIT(6)

#define MCP3564_MUX_REG                 0x06
#define MCP3564_MUX_VIN_P_MASK                  GENMASK(7, 4)
#define MCP3564_MUX_VIN_N_MASK                  GENMASK(3, 0)
#define MCP3564_MUX_SET(x, y)                   (FIELD_PREP(MCP3564_MUX_VIN_P_MASK, (x)) |      \
                                                FIELD_PREP(MCP3564_MUX_VIN_N_MASK, (y)))

#define MCP3564_SCAN_REG                0x07
#define MCP3564_SCAN_CH_SEL_MASK                GENMASK(15, 0)
#define MCP3564_SCAN_CH_SEL_SET(x)              FIELD_PREP(MCP3564_SCAN_CH_SEL_MASK, (x))
#define MCP3564_SCAN_DELAY_TIME_MASK            GENMASK(23, 21)
#define MCP3564_SCAN_DELAY_TIME_SET(x)          FIELD_PREP(MCP3564_SCAN_DELAY_TIME_MASK, (x))
#define MCP3564_SCAN_DEFAULT_VALUE              0

#define MCP3564_TIMER_REG               0x08
#define MCP3564_TIMER_DEFAULT_VALUE             0

#define MCP3564_OFFSETCAL_REG           0x09
#define MCP3564_DEFAULT_OFFSETCAL               0

#define MCP3564_GAINCAL_REG             0x0A
#define MCP3564_DEFAULT_GAINCAL                 0x00800000

#define MCP3564_RESERVED_B_REG          0x0B

#define MCP3564_RESERVED_C_REG          0x0C
#define MCP3564_C_REG_DEFAULT                   0x50
#define MCP3564R_C_REG_DEFAULT                  0x30

#define MCP3564_LOCK_REG                0x0D
#define MCP3564_LOCK_WRITE_ACCESS_PASSWORD      0xA5
#define MCP3564_RESERVED_E_REG          0x0E
#define MCP3564_CRCCFG_REG              0x0F

#define MCP3564_CMD_HW_ADDR_MASK        GENMASK(7, 6)
#define MCP3564_CMD_ADDR_MASK           GENMASK(5, 2)

#define MCP3564_HW_ADDR_MASK            GENMASK(1, 0)

#define MCP3564_FASTCMD_START   0x0A
#define MCP3564_FASTCMD_RESET   0x0E

#define MCP3461_HW_ID           0x0008
#define MCP3462_HW_ID           0x0009
#define MCP3464_HW_ID           0x000B

#define MCP3561_HW_ID           0x000C
#define MCP3562_HW_ID           0x000D
#define MCP3564_HW_ID           0x000F
#define MCP3564_HW_ID_MASK      GENMASK(3, 0)

#define MCP3564R_INT_VREF_MV    2400

#define MCP3564_DATA_READY_TIMEOUT_MS   2000

#define MCP3564_MAX_PGA                         8
#define MCP3564_MAX_BURNOUT_IDX                 4
#define MCP3564_MAX_CHANNELS                    66

enum mcp3564_ids {
        mcp3461,
        mcp3462,
        mcp3464,
        mcp3561,
        mcp3562,
        mcp3564,
        mcp3461r,
        mcp3462r,
        mcp3464r,
        mcp3561r,
        mcp3562r,
        mcp3564r,
};

enum mcp3564_delay_time {
        MCP3564_NO_DELAY,
        MCP3564_DELAY_8_DMCLK,
        MCP3564_DELAY_16_DMCLK,
        MCP3564_DELAY_32_DMCLK,
        MCP3564_DELAY_64_DMCLK,
        MCP3564_DELAY_128_DMCLK,
        MCP3564_DELAY_256_DMCLK,
        MCP3564_DELAY_512_DMCLK
};

enum mcp3564_adc_conversion_mode {
        MCP3564_ADC_MODE_DEFAULT,
        MCP3564_ADC_MODE_SHUTDOWN,
        MCP3564_ADC_MODE_STANDBY,
        MCP3564_ADC_MODE_CONVERSION
};

enum mcp3564_adc_bias_current {
        MCP3564_BOOST_CURRENT_x0_50,
        MCP3564_BOOST_CURRENT_x0_66,
        MCP3564_BOOST_CURRENT_x1_00,
        MCP3564_BOOST_CURRENT_x2_00
};

enum mcp3564_burnout {
        MCP3564_CONFIG0_CS_SEL_0_0_uA,
        MCP3564_CONFIG0_CS_SEL_0_9_uA,
        MCP3564_CONFIG0_CS_SEL_3_7_uA,
        MCP3564_CONFIG0_CS_SEL_15_uA
};

enum mcp3564_channel_names {
        MCP3564_CH0,
        MCP3564_CH1,
        MCP3564_CH2,
        MCP3564_CH3,
        MCP3564_CH4,
        MCP3564_CH5,
        MCP3564_CH6,
        MCP3564_CH7,
        MCP3564_AGND,
        MCP3564_AVDD,
        MCP3564_RESERVED, /* do not use */
        MCP3564_REFIN_POZ,
        MCP3564_REFIN_NEG,
        MCP3564_TEMP_DIODE_P,
        MCP3564_TEMP_DIODE_M,
        MCP3564_INTERNAL_VCM,
};

enum mcp3564_oversampling {
        MCP3564_OVERSAMPLING_RATIO_32,
        MCP3564_OVERSAMPLING_RATIO_64,
        MCP3564_OVERSAMPLING_RATIO_128,
        MCP3564_OVERSAMPLING_RATIO_256,
        MCP3564_OVERSAMPLING_RATIO_512,
        MCP3564_OVERSAMPLING_RATIO_1024,
        MCP3564_OVERSAMPLING_RATIO_2048,
        MCP3564_OVERSAMPLING_RATIO_4096,
        MCP3564_OVERSAMPLING_RATIO_8192,
        MCP3564_OVERSAMPLING_RATIO_16384,
        MCP3564_OVERSAMPLING_RATIO_20480,
        MCP3564_OVERSAMPLING_RATIO_24576,
        MCP3564_OVERSAMPLING_RATIO_40960,
        MCP3564_OVERSAMPLING_RATIO_49152,
        MCP3564_OVERSAMPLING_RATIO_81920,
        MCP3564_OVERSAMPLING_RATIO_98304
};

static const unsigned int mcp3564_oversampling_avail[] = {
        [MCP3564_OVERSAMPLING_RATIO_32] = 32,
        [MCP3564_OVERSAMPLING_RATIO_64] = 64,
        [MCP3564_OVERSAMPLING_RATIO_128] = 128,
        [MCP3564_OVERSAMPLING_RATIO_256] = 256,
        [MCP3564_OVERSAMPLING_RATIO_512] = 512,
        [MCP3564_OVERSAMPLING_RATIO_1024] = 1024,
        [MCP3564_OVERSAMPLING_RATIO_2048] = 2048,
        [MCP3564_OVERSAMPLING_RATIO_4096] = 4096,
        [MCP3564_OVERSAMPLING_RATIO_8192] = 8192,
        [MCP3564_OVERSAMPLING_RATIO_16384] = 16384,
        [MCP3564_OVERSAMPLING_RATIO_20480] = 20480,
        [MCP3564_OVERSAMPLING_RATIO_24576] = 24576,
        [MCP3564_OVERSAMPLING_RATIO_40960] = 40960,
        [MCP3564_OVERSAMPLING_RATIO_49152] = 49152,
        [MCP3564_OVERSAMPLING_RATIO_81920] = 81920,
        [MCP3564_OVERSAMPLING_RATIO_98304] = 98304
};

/*
 * Current Source/Sink Selection Bits for Sensor Bias (source on VIN+/sink on VIN-)
 */
static const int mcp3564_burnout_avail[][2] = {
        [MCP3564_CONFIG0_CS_SEL_0_0_uA] = { 0, 0 },
        [MCP3564_CONFIG0_CS_SEL_0_9_uA] = { 0, 900 },
        [MCP3564_CONFIG0_CS_SEL_3_7_uA] = { 0, 3700 },
        [MCP3564_CONFIG0_CS_SEL_15_uA] = { 0, 15000 }
};

/*
 * BOOST[1:0]: ADC Bias Current Selection
 */
static const char * const mcp3564_boost_current_avail[] = {
        [MCP3564_BOOST_CURRENT_x0_50] = "0.5",
        [MCP3564_BOOST_CURRENT_x0_66] = "0.66",
        [MCP3564_BOOST_CURRENT_x1_00] = "1",
        [MCP3564_BOOST_CURRENT_x2_00] = "2",
};

/*
 * Calibration bias values
 */
static const int mcp3564_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 mcp3564_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 mcp3564_hwgain_frac[] = {
        3, 10,
        1, 1,
        2, 1,
        4, 1,
        8, 1,
        16, 1,
        32, 1,
        64, 1
};

static const char *mcp3564_channel_labels[2] = {
        "burnout_current", "temperature",
};

/**
 * struct mcp3564_chip_info - chip specific data
 * @name:               device name
 * @num_channels:       number of channels
 * @resolution:         ADC resolution
 * @have_vref:          does the hardware have an internal voltage reference?
 */
struct mcp3564_chip_info {
        const char      *name;
        unsigned int    num_channels;
        unsigned int    resolution;
        bool            have_vref;
};

/**
 * struct mcp3564_state - working data for a ADC device
 * @chip_info:          chip specific data
 * @spi:                SPI device structure
 * @vref_mv:            voltage reference value in miliVolts
 * @lock:               synchronize access to driver's state members
 * @dev_addr:           hardware device address
 * @oversampling:       the index inside oversampling list of the ADC
 * @hwgain:             the index inside hardware gain list of the ADC
 * @scale_tbls:         table with precalculated scale
 * @calib_bias:         calibration bias value
 * @calib_scale:        calibration scale value
 * @current_boost_mode: the index inside current boost list of the ADC
 * @burnout_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
 * @have_vref:          does the ADC have an internal voltage reference?
 * @labels:             table with channels labels
 */
struct mcp3564_state {
        const struct mcp3564_chip_info  *chip_info;
        struct spi_device               *spi;
        unsigned short                  vref_mv;
        struct mutex                    lock; /* Synchronize access to driver's state members */
        u8                              dev_addr;
        enum mcp3564_oversampling       oversampling;
        unsigned int                    hwgain;
        unsigned int                    scale_tbls[MCP3564_MAX_PGA][2];
        int                             calib_bias;
        int                             calib_scale;
        unsigned int                    current_boost_mode;
        enum mcp3564_burnout            burnout_mode;
        bool                            auto_zeroing_mux;
        bool                            auto_zeroing_ref;
        bool                            have_vref;
        const char                      *labels[MCP3564_MAX_CHANNELS];
};

static inline u8 mcp3564_cmd_write(u8 chip_addr, u8 reg)
{
        return FIELD_PREP(MCP3564_CMD_HW_ADDR_MASK, chip_addr) |
               FIELD_PREP(MCP3564_CMD_ADDR_MASK, reg) |
               BIT(1);
}

static inline u8 mcp3564_cmd_read(u8 chip_addr, u8 reg)
{
        return FIELD_PREP(MCP3564_CMD_HW_ADDR_MASK, chip_addr) |
               FIELD_PREP(MCP3564_CMD_ADDR_MASK, reg) |
               BIT(0);
}

static int mcp3564_read_8bits(struct mcp3564_state *adc, u8 reg, u8 *val)
{
        int ret;
        u8 tx_buf;
        u8 rx_buf;

        tx_buf = mcp3564_cmd_read(adc->dev_addr, reg);

        ret = spi_write_then_read(adc->spi, &tx_buf, sizeof(tx_buf),
                                  &rx_buf, sizeof(rx_buf));
        *val = rx_buf;

        return ret;
}

static int mcp3564_read_16bits(struct mcp3564_state *adc, u8 reg, u16 *val)
{
        int ret;
        u8 tx_buf;
        __be16 rx_buf;

        tx_buf = mcp3564_cmd_read(adc->dev_addr, reg);

        ret = spi_write_then_read(adc->spi, &tx_buf, sizeof(tx_buf),
                                  &rx_buf, sizeof(rx_buf));
        *val = be16_to_cpu(rx_buf);

        return ret;
}

static int mcp3564_read_32bits(struct mcp3564_state *adc, u8 reg, u32 *val)
{
        int ret;
        u8 tx_buf;
        __be32 rx_buf;

        tx_buf = mcp3564_cmd_read(adc->dev_addr, reg);

        ret = spi_write_then_read(adc->spi, &tx_buf, sizeof(tx_buf),
                                  &rx_buf, sizeof(rx_buf));
        *val = be32_to_cpu(rx_buf);

        return ret;
}

static int mcp3564_write_8bits(struct mcp3564_state *adc, u8 reg, u8 val)
{
        u8 tx_buf[2];

        tx_buf[0] = mcp3564_cmd_write(adc->dev_addr, reg);
        tx_buf[1] = val;

        return spi_write_then_read(adc->spi, tx_buf, sizeof(tx_buf), NULL, 0);
}

static int mcp3564_write_24bits(struct mcp3564_state *adc, u8 reg, u32 val)
{
        __be32 val_be;

        val |= (mcp3564_cmd_write(adc->dev_addr, reg) << 24);
        val_be = cpu_to_be32(val);

        return spi_write_then_read(adc->spi, &val_be, sizeof(val_be), NULL, 0);
}

static int mcp3564_fast_cmd(struct mcp3564_state *adc, u8 fast_cmd)
{
        u8 val;

        val = FIELD_PREP(MCP3564_CMD_HW_ADDR_MASK, adc->dev_addr) |
                         FIELD_PREP(MCP3564_CMD_ADDR_MASK, fast_cmd);

        return spi_write_then_read(adc->spi, &val, 1, NULL, 0);
}

static int mcp3564_update_8bits(struct mcp3564_state *adc, u8 reg, u32 mask, u8 val)
{
        u8 tmp;
        int ret;

        val &= mask;

        ret = mcp3564_read_8bits(adc, reg, &tmp);
        if (ret < 0)
                return ret;

        tmp &= ~mask;
        tmp |= val;

        return mcp3564_write_8bits(adc, reg, tmp);
}

static int mcp3564_set_current_boost_mode(struct iio_dev *indio_dev,
                                          const struct iio_chan_spec *chan,
                                          unsigned int mode)
{
        struct mcp3564_state *adc = iio_priv(indio_dev);
        int ret;

        dev_dbg(&indio_dev->dev, "%s: %d\n", __func__, mode);

        mutex_lock(&adc->lock);
        ret = mcp3564_update_8bits(adc, MCP3564_CONFIG2_REG, MCP3564_CONFIG2_BOOST_CURRENT_MASK,
                                   FIELD_PREP(MCP3564_CONFIG2_BOOST_CURRENT_MASK, mode));

        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 mcp3564_get_current_boost_mode(struct iio_dev *indio_dev,
                                          const struct iio_chan_spec *chan)
{
        struct mcp3564_state *adc = iio_priv(indio_dev);

        return adc->current_boost_mode;
}

static const struct iio_enum mcp3564_current_boost_mode_enum = {
        .items = mcp3564_boost_current_avail,
        .num_items = ARRAY_SIZE(mcp3564_boost_current_avail),
        .set = mcp3564_set_current_boost_mode,
        .get = mcp3564_get_current_boost_mode,
};

static const struct iio_chan_spec_ext_info mcp3564_ext_info[] = {
        IIO_ENUM("boost_current_gain", IIO_SHARED_BY_ALL, &mcp3564_current_boost_mode_enum),
        {
                .name = "boost_current_gain_available",
                .shared = IIO_SHARED_BY_ALL,
                .read = iio_enum_available_read,
                .private = (uintptr_t)&mcp3564_current_boost_mode_enum,
        },
        { }
};

static ssize_t mcp3564_auto_zeroing_mux_show(struct device *dev,
                                             struct device_attribute *attr,
                                             char *buf)
{
        struct iio_dev *indio_dev = dev_to_iio_dev(dev);
        struct mcp3564_state *adc = iio_priv(indio_dev);

        return sysfs_emit(buf, "%d\n", adc->auto_zeroing_mux);
}

static ssize_t mcp3564_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 mcp3564_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 = mcp3564_update_8bits(adc, MCP3564_CONFIG2_REG, MCP3564_CONFIG2_AZ_MUX_MASK,
                                   FIELD_PREP(MCP3564_CONFIG2_AZ_MUX_MASK, auto_zero));

        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 mcp3564_auto_zeroing_ref_show(struct device *dev,
                                             struct device_attribute *attr,
                                             char *buf)
{
        struct iio_dev *indio_dev = dev_to_iio_dev(dev);
        struct mcp3564_state *adc = iio_priv(indio_dev);

        return sysfs_emit(buf, "%d\n", adc->auto_zeroing_ref);
}

static ssize_t mcp3564_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 mcp3564_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 = mcp3564_update_8bits(adc, MCP3564_CONFIG2_REG, MCP3564_CONFIG2_AZ_REF_MASK,
                                   FIELD_PREP(MCP3564_CONFIG2_AZ_REF_MASK, auto_zero));

        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;
}

static const struct iio_chan_spec mcp3564_channel_template = {
        .type = IIO_VOLTAGE,
        .indexed = 1,
        .differential = 1,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_shared_by_all  = BIT(IIO_CHAN_INFO_SCALE)            |
                                BIT(IIO_CHAN_INFO_CALIBSCALE)           |
                                BIT(IIO_CHAN_INFO_CALIBBIAS)            |
                                BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
        .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SCALE)   |
                                BIT(IIO_CHAN_INFO_CALIBSCALE) |
                                BIT(IIO_CHAN_INFO_CALIBBIAS)            |
                                BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
        .ext_info = mcp3564_ext_info,
};

static const struct iio_chan_spec mcp3564_temp_channel_template = {
        .type = IIO_TEMP,
        .channel = 0,
        .address = ((MCP3564_TEMP_DIODE_P << 4) | MCP3564_TEMP_DIODE_M),
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_shared_by_all  = BIT(IIO_CHAN_INFO_SCALE)            |
                        BIT(IIO_CHAN_INFO_CALIBSCALE)                   |
                        BIT(IIO_CHAN_INFO_CALIBBIAS)                    |
                        BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
        .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SCALE)   |
                        BIT(IIO_CHAN_INFO_CALIBSCALE) |
                        BIT(IIO_CHAN_INFO_CALIBBIAS)                    |
                        BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
};

static const struct iio_chan_spec mcp3564_burnout_channel_template = {
        .type = IIO_CURRENT,
        .output = true,
        .channel = 0,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW),
};

/*
 * Number of channels could be calculated:
 * num_channels = single_ended_input + differential_input + temperature + burnout
 * Eg. for MCP3561 (only 2 channels available: CH0 and CH1)
 * single_ended_input = (CH0 - GND), (CH1 -  GND) = 2
 * differential_input = (CH0 - CH1), (CH0 -  CH0) = 2
 * num_channels = 2 + 2 + 2
 * Generic formula is:
 * num_channels = P^R(Number_of_single_ended_channels, 2) + 2 (temperature + burnout channels)
 * P^R(Number_of_single_ended_channels, 2) is Permutations with Replacement of
 *     Number_of_single_ended_channels taken by 2
 */
static const struct mcp3564_chip_info mcp3564_chip_infos_tbl[] = {
        [mcp3461] = {
                .name = "mcp3461",
                .num_channels = 6,
                .resolution = 16,
                .have_vref = false,
        },
        [mcp3462] = {
                .name = "mcp3462",
                .num_channels = 18,
                .resolution = 16,
                .have_vref = false,
        },
        [mcp3464] = {
                .name = "mcp3464",
                .num_channels = 66,
                .resolution = 16,
                .have_vref = false,
        },
        [mcp3561] = {
                .name = "mcp3561",
                .num_channels = 6,
                .resolution = 24,
                .have_vref = false,
        },
        [mcp3562] = {
                .name = "mcp3562",
                .num_channels = 18,
                .resolution = 24,
                .have_vref = false,
        },
        [mcp3564] = {
                .name = "mcp3564",
                .num_channels = 66,
                .resolution = 24,
                .have_vref = false,
        },
        [mcp3461r] = {
                .name = "mcp3461r",
                .num_channels = 6,
                .resolution = 16,
                .have_vref = false,
        },
        [mcp3462r] = {
                .name = "mcp3462r",
                .num_channels = 18,
                .resolution = 16,
                .have_vref = true,
        },
        [mcp3464r] = {
                .name = "mcp3464r",
                .num_channels = 66,
                .resolution = 16,
                .have_vref = true,
        },
        [mcp3561r] = {
                .name = "mcp3561r",
                .num_channels = 6,
                .resolution = 24,
                .have_vref = true,
        },
        [mcp3562r] = {
                .name = "mcp3562r",
                .num_channels = 18,
                .resolution = 24,
                .have_vref = true,
        },
        [mcp3564r] = {
                .name = "mcp3564r",
                .num_channels = 66,
                .resolution = 24,
                .have_vref = true,
        },
};

static int mcp3564_read_single_value(struct iio_dev *indio_dev,
                                     struct iio_chan_spec const *channel,
                                     int *val)
{
        struct mcp3564_state *adc = iio_priv(indio_dev);
        int ret;
        u8 tmp;
        int ret_read = 0;

        ret = mcp3564_write_8bits(adc, MCP3564_MUX_REG, channel->address);
        if (ret)
                return ret;

        /* Start ADC Conversion using fast command (overwrites ADC_MODE[1:0] = 11) */
        ret = mcp3564_fast_cmd(adc, MCP3564_FASTCMD_START);
        if (ret)
                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(mcp3564_read_8bits, ret_read,
                                ret_read || !(tmp & MCP3564_DATA_READY_MASK),
                                20000, MCP3564_DATA_READY_TIMEOUT_MS * 1000, true,
                                adc, MCP3564_IRQ_REG, &tmp);

        /* failed to read status register */
        if (ret_read)
                return ret_read;

        if (ret)
                return ret;

        if (tmp & MCP3564_DATA_READY_MASK)
                /* failing to finish conversion */
                return -EBUSY;

        return mcp3564_read_32bits(adc, MCP3564_ADCDATA_REG, val);
}

static int mcp3564_read_avail(struct iio_dev *indio_dev,
                              struct iio_chan_spec const *channel,
                              const int **vals, int *type,
                              int *length, long mask)
{
        struct mcp3564_state *adc = iio_priv(indio_dev);

        switch (mask) {
        case IIO_CHAN_INFO_RAW:
                if (!channel->output)
                        return -EINVAL;

                *vals = mcp3564_burnout_avail[0];
                *length = ARRAY_SIZE(mcp3564_burnout_avail) * 2;
                *type = IIO_VAL_INT_PLUS_MICRO;
                return IIO_AVAIL_LIST;
        case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
                *vals = mcp3564_oversampling_avail;
                *length = ARRAY_SIZE(mcp3564_oversampling_avail);
                *type = IIO_VAL_INT;
                return IIO_AVAIL_LIST;
        case IIO_CHAN_INFO_SCALE:
                *vals = (int *)adc->scale_tbls;
                *length = ARRAY_SIZE(adc->scale_tbls) * 2;
                *type = IIO_VAL_INT_PLUS_NANO;
                return IIO_AVAIL_LIST;
        case IIO_CHAN_INFO_CALIBBIAS:
                *vals = mcp3564_calib_bias;
                *type = IIO_VAL_INT;
                return IIO_AVAIL_RANGE;
        case IIO_CHAN_INFO_CALIBSCALE:
                *vals = mcp3564_calib_scale;
                *type = IIO_VAL_INT;
                return IIO_AVAIL_RANGE;
        default:
                return -EINVAL;
        }
}

static int mcp3564_read_raw(struct iio_dev *indio_dev,
                            struct iio_chan_spec const *channel,
                            int *val, int *val2, long mask)
{
        struct mcp3564_state *adc = iio_priv(indio_dev);
        int ret;

        switch (mask) {
        case IIO_CHAN_INFO_RAW:
                if (channel->output) {
                        mutex_lock(&adc->lock);
                        *val = mcp3564_burnout_avail[adc->burnout_mode][0];
                        *val2 = mcp3564_burnout_avail[adc->burnout_mode][1];
                        mutex_unlock(&adc->lock);
                        return IIO_VAL_INT_PLUS_MICRO;
                }

                ret = mcp3564_read_single_value(indio_dev, channel, val);
                if (ret)
                        return -EINVAL;
                return IIO_VAL_INT;
        case IIO_CHAN_INFO_SCALE:
                mutex_lock(&adc->lock);
                *val = adc->scale_tbls[adc->hwgain][0];
                *val2 = adc->scale_tbls[adc->hwgain][1];
                mutex_unlock(&adc->lock);
                return IIO_VAL_INT_PLUS_NANO;
        case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
                *val = mcp3564_oversampling_avail[adc->oversampling];
                return IIO_VAL_INT;
        case IIO_CHAN_INFO_CALIBBIAS:
                *val = adc->calib_bias;
                return IIO_VAL_INT;
        case IIO_CHAN_INFO_CALIBSCALE:
                *val = adc->calib_scale;
                return IIO_VAL_INT;
        default:
                return -EINVAL;
        }
}

static int mcp3564_write_raw_get_fmt(struct iio_dev *indio_dev,
                                     struct iio_chan_spec const *chan,
                                     long info)
{
        switch (info) {
        case IIO_CHAN_INFO_RAW:
                return IIO_VAL_INT_PLUS_MICRO;
        case IIO_CHAN_INFO_CALIBBIAS:
        case IIO_CHAN_INFO_CALIBSCALE:
        case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
                return IIO_VAL_INT;
        case IIO_CHAN_INFO_SCALE:
                return IIO_VAL_INT_PLUS_NANO;
        default:
                return -EINVAL;
        }
}

static int mcp3564_write_raw(struct iio_dev *indio_dev,
                             struct iio_chan_spec const *channel, int val,
                             int val2, long mask)
{
        struct mcp3564_state *adc = iio_priv(indio_dev);
        int tmp;
        unsigned int hwgain;
        enum mcp3564_burnout burnout;
        int ret = 0;

        switch (mask) {
        case IIO_CHAN_INFO_RAW:
                if (!channel->output)
                        return -EINVAL;

                for (burnout = 0; burnout < MCP3564_MAX_BURNOUT_IDX; burnout++)
                        if (val == mcp3564_burnout_avail[burnout][0] &&
                            val2 == mcp3564_burnout_avail[burnout][1])
                                break;

                if (burnout == MCP3564_MAX_BURNOUT_IDX)
                        return -EINVAL;

                if (burnout == adc->burnout_mode)
                        return ret;

                mutex_lock(&adc->lock);
                ret = mcp3564_update_8bits(adc, MCP3564_CONFIG0_REG,
                                           MCP3564_CONFIG0_CS_SEL_MASK,
                                           FIELD_PREP(MCP3564_CONFIG0_CS_SEL_MASK, burnout));

                if (ret)
                        dev_err(&indio_dev->dev, "Failed to configure burnout current\n");
                else
                        adc->burnout_mode = burnout;
                mutex_unlock(&adc->lock);
                return ret;
        case IIO_CHAN_INFO_CALIBBIAS:
                if (val < mcp3564_calib_bias[0] || val > mcp3564_calib_bias[2])
                        return -EINVAL;

                mutex_lock(&adc->lock);
                ret = mcp3564_write_24bits(adc, MCP3564_OFFSETCAL_REG, val);
                if (!ret)
                        adc->calib_bias = val;
                mutex_unlock(&adc->lock);
                return ret;
        case IIO_CHAN_INFO_CALIBSCALE:
                if (val < mcp3564_calib_scale[0] || val > mcp3564_calib_scale[2])
                        return -EINVAL;

                if (adc->calib_scale == val)
                        return ret;

                mutex_lock(&adc->lock);
                ret = mcp3564_write_24bits(adc, MCP3564_GAINCAL_REG, val);
                if (!ret)
                        adc->calib_scale = val;
                mutex_unlock(&adc->lock);
                return ret;
        case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
                if (val < 0)
                        return -EINVAL;

                tmp = find_closest(val, mcp3564_oversampling_avail,
                                   ARRAY_SIZE(mcp3564_oversampling_avail));

                if (adc->oversampling == tmp)
                        return ret;

                mutex_lock(&adc->lock);
                ret = mcp3564_update_8bits(adc, MCP3564_CONFIG1_REG,
                                           MCP3564_CONFIG1_OVERSPL_RATIO_MASK,
                                           FIELD_PREP(MCP3564_CONFIG1_OVERSPL_RATIO_MASK,
                                                      adc->oversampling));
                if (!ret)
                        adc->oversampling = tmp;
                mutex_unlock(&adc->lock);
                return ret;
        case IIO_CHAN_INFO_SCALE:
                for (hwgain = 0; hwgain < MCP3564_MAX_PGA; hwgain++)
                        if (val == adc->scale_tbls[hwgain][0] &&
                            val2 == adc->scale_tbls[hwgain][1])
                                break;

                if (hwgain == MCP3564_MAX_PGA)
                        return -EINVAL;

                if (hwgain == adc->hwgain)
                        return ret;

                mutex_lock(&adc->lock);
                ret = mcp3564_update_8bits(adc, MCP3564_CONFIG2_REG,
                                           MCP3564_CONFIG2_HARDWARE_GAIN_MASK,
                                           FIELD_PREP(MCP3564_CONFIG2_HARDWARE_GAIN_MASK, hwgain));
                if (!ret)
                        adc->hwgain = hwgain;

                mutex_unlock(&adc->lock);
                return ret;
        default:
                return -EINVAL;
        }
}

static int mcp3564_read_label(struct iio_dev *indio_dev,
                              struct iio_chan_spec const *chan, char *label)
{
        struct mcp3564_state *adc = iio_priv(indio_dev);

        return sysfs_emit(label, "%s\n", adc->labels[chan->scan_index]);
}

static int mcp3564_parse_fw_children(struct iio_dev *indio_dev)
{
        struct mcp3564_state *adc = iio_priv(indio_dev);
        struct device *dev = &adc->spi->dev;
        struct iio_chan_spec *channels;
        struct iio_chan_spec chanspec = mcp3564_channel_template;
        struct iio_chan_spec temp_chanspec = mcp3564_temp_channel_template;
        struct iio_chan_spec burnout_chanspec = mcp3564_burnout_channel_template;
        int chan_idx = 0;
        unsigned int num_ch;
        u32 inputs[2];
        const char *node_name;
        const char *label;
        int ret;

        num_ch = device_get_child_node_count(dev);
        if (num_ch == 0)
                return dev_err_probe(&indio_dev->dev, -ENODEV,
                                     "FW has no channels defined\n");

        /* Reserve space for burnout and temperature channel */
        num_ch += 2;

        if (num_ch > adc->chip_info->num_channels)
                return dev_err_probe(dev, -EINVAL, "Too many channels %d > %d\n",
                                     num_ch, adc->chip_info->num_channels);

        channels = devm_kcalloc(dev, num_ch, sizeof(*channels), GFP_KERNEL);
        if (!channels)
                return -ENOMEM;

        device_for_each_child_node_scoped(dev, child) {
                node_name = fwnode_get_name(child);

                if (fwnode_property_present(child, "diff-channels")) {
                        ret = fwnode_property_read_u32_array(child,
                                                             "diff-channels",
                                                             inputs,
                                                             ARRAY_SIZE(inputs));
                        if (ret)
                                return ret;

                        chanspec.differential = 1;
                } else {
                        ret = fwnode_property_read_u32(child, "reg", &inputs[0]);
                        if (ret)
                                return ret;

                        chanspec.differential = 0;
                        inputs[1] = MCP3564_AGND;
                }

                if (inputs[0] > MCP3564_INTERNAL_VCM ||
                    inputs[1] > MCP3564_INTERNAL_VCM)
                        return dev_err_probe(&indio_dev->dev, -EINVAL,
                                             "Channel index > %d, for %s\n",
                                             MCP3564_INTERNAL_VCM + 1,
                                             node_name);

                chanspec.address = (inputs[0] << 4) | inputs[1];
                chanspec.channel = inputs[0];
                chanspec.channel2 = inputs[1];
                chanspec.scan_index = chan_idx;

                if (fwnode_property_present(child, "label")) {
                        fwnode_property_read_string(child, "label", &label);
                        adc->labels[chan_idx] = label;
                }

                channels[chan_idx] = chanspec;
                chan_idx++;
        }

        /* Add burnout current channel */
        burnout_chanspec.scan_index = chan_idx;
        channels[chan_idx] = burnout_chanspec;
        adc->labels[chan_idx] = mcp3564_channel_labels[0];
        chanspec.scan_index = chan_idx;
        chan_idx++;

        /* Add temperature channel */
        temp_chanspec.scan_index = chan_idx;
        channels[chan_idx] = temp_chanspec;
        adc->labels[chan_idx] = mcp3564_channel_labels[1];
        chan_idx++;

        indio_dev->num_channels = chan_idx;
        indio_dev->channels = channels;

        return 0;
}

static void mcp3564_fill_scale_tbls(struct mcp3564_state *adc)
{
        unsigned int pow = adc->chip_info->resolution - 1;
        int ref;
        unsigned int i;
        int tmp0;
        u64 tmp1;

        for (i = 0; i < MCP3564_MAX_PGA; i++) {
                ref = adc->vref_mv;
                tmp1 = ((u64)ref * NANO) >> pow;
                div_u64_rem(tmp1, NANO, &tmp0);

                tmp1 = tmp1 * mcp3564_hwgain_frac[(2 * i) + 1];
                tmp0 = (int)div_u64(tmp1, mcp3564_hwgain_frac[2 * i]);

                adc->scale_tbls[i][1] = tmp0;
        }
}

static int mcp3564_config(struct iio_dev *indio_dev, bool *use_internal_vref_attr)
{
        struct mcp3564_state *adc = iio_priv(indio_dev);
        struct device *dev = &adc->spi->dev;
        u8 tmp_reg;
        u16 tmp_u16;
        enum mcp3564_ids ids;
        int ret = 0;
        unsigned int tmp = 0x01;
        bool internal_vref;
        bool err = false;

        /*
         * 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(dev, "microchip,hw-device-address", &tmp);

        if (tmp > 3) {
                dev_err_probe(dev, tmp,
                              "invalid device address. Must be in range 0-3.\n");
                return -EINVAL;
        }

        adc->dev_addr = FIELD_GET(MCP3564_HW_ADDR_MASK, tmp);

        dev_dbg(dev, "use HW device address %i\n", adc->dev_addr);

        ret = mcp3564_read_8bits(adc, MCP3564_RESERVED_C_REG, &tmp_reg);
        if (ret < 0)
                return ret;

        switch (tmp_reg) {
        case MCP3564_C_REG_DEFAULT:
                adc->have_vref = false;
                break;
        case MCP3564R_C_REG_DEFAULT:
                adc->have_vref = true;
                break;
        default:
                dev_info(dev, "Unknown chip found: %d\n", tmp_reg);
                err = true;
        }

        if (!err) {
                ret = mcp3564_read_16bits(adc, MCP3564_RESERVED_E_REG, &tmp_u16);
                if (ret < 0)
                        return ret;

                switch (tmp_u16 & MCP3564_HW_ID_MASK) {
                case MCP3461_HW_ID:
                        if (adc->have_vref)
                                ids = mcp3461r;
                        else
                                ids = mcp3461;
                        break;
                case MCP3462_HW_ID:
                        if (adc->have_vref)
                                ids = mcp3462r;
                        else
                                ids = mcp3462;
                        break;
                case MCP3464_HW_ID:
                        if (adc->have_vref)
                                ids = mcp3464r;
                        else
                                ids = mcp3464;
                        break;
                case MCP3561_HW_ID:
                        if (adc->have_vref)
                                ids = mcp3561r;
                        else
                                ids = mcp3561;
                        break;
                case MCP3562_HW_ID:
                        if (adc->have_vref)
                                ids = mcp3562r;
                        else
                                ids = mcp3562;
                        break;
                case MCP3564_HW_ID:
                        if (adc->have_vref)
                                ids = mcp3564r;
                        else
                                ids = mcp3564;
                        break;
                default:
                        dev_info(dev, "Unknown chip found: %d\n", tmp_u16);
                        err = true;
                }
        }

        if (err) {
                /*
                 * If failed to identify the hardware based on internal registers,
                 * try using fallback compatible in device tree to deal with some newer part number.
                 */
                adc->chip_info = spi_get_device_match_data(adc->spi);
                adc->have_vref = adc->chip_info->have_vref;
        } else {
                adc->chip_info = &mcp3564_chip_infos_tbl[ids];
        }

        dev_dbg(dev, "Found %s chip\n", adc->chip_info->name);

        ret = devm_regulator_get_enable_read_voltage(dev, "vref");
        if (ret < 0 && ret != -ENODEV)
                return dev_err_probe(dev, ret, "Failed to get vref voltage\n");

        internal_vref = ret == -ENODEV;
        adc->vref_mv = internal_vref ? MCP3564R_INT_VREF_MV : ret / MILLI;
        *use_internal_vref_attr = internal_vref;

        if (internal_vref) {
                /* Check if chip has internal vref */
                if (!adc->have_vref)
                        return dev_err_probe(dev, -ENODEV, "Unknown Vref\n");

                dev_dbg(dev, "%s: Using internal Vref\n", __func__);
        } else {
                dev_dbg(dev, "%s: Using External Vref\n", __func__);
        }

        ret = mcp3564_parse_fw_children(indio_dev);
        if (ret)
                return ret;

        /*
         * Command sequence that ensures a recovery with the desired settings
         * in any cases of loss-of-power scenario (Full Chip Reset):
         *  - Write LOCK register to 0xA5
         *  - Write IRQ register to 0x03
         *  - Send "Device Full Reset" fast command
         *  - Wait 1ms for "Full Reset" to complete
         */
        ret = mcp3564_write_8bits(adc, MCP3564_LOCK_REG, MCP3564_LOCK_WRITE_ACCESS_PASSWORD);
        if (ret)
                return ret;

        ret = mcp3564_write_8bits(adc, MCP3564_IRQ_REG, 0x03);
        if (ret)
                return ret;

        ret = mcp3564_fast_cmd(adc, MCP3564_FASTCMD_RESET);
        if (ret)
                return ret;

        /*
         * After Full reset wait some time to be able to fully reset the part and place
         * it back in a default configuration.
         * From datasheet: POR (Power On Reset Time) is ~1us
         * 1ms should be enough.
         */
        mdelay(1);

        /* set a gain of 1x for GAINCAL */
        ret = mcp3564_write_24bits(adc, MCP3564_GAINCAL_REG, MCP3564_DEFAULT_GAINCAL);
        if (ret)
                return ret;

        adc->calib_scale = MCP3564_DEFAULT_GAINCAL;

        ret = mcp3564_write_24bits(adc, MCP3564_OFFSETCAL_REG, MCP3564_DEFAULT_OFFSETCAL);
        if (ret)
                return ret;

        ret = mcp3564_write_24bits(adc, MCP3564_TIMER_REG, MCP3564_TIMER_DEFAULT_VALUE);
        if (ret)
                return ret;

        ret = mcp3564_write_24bits(adc, MCP3564_SCAN_REG,
                                   MCP3564_SCAN_DELAY_TIME_SET(MCP3564_NO_DELAY) |
                                   MCP3564_SCAN_CH_SEL_SET(MCP3564_SCAN_DEFAULT_VALUE));
        if (ret)
                return ret;

        ret = mcp3564_write_8bits(adc, MCP3564_MUX_REG, MCP3564_MUX_SET(MCP3564_CH0, MCP3564_CH1));
        if (ret)
                return ret;

        ret = mcp3564_write_8bits(adc, MCP3564_IRQ_REG,
                                  FIELD_PREP(MCP3464_EN_FASTCMD_MASK, 1) |
                                  FIELD_PREP(MCP3464_EN_STP_MASK, 1));
        if (ret)
                return ret;

        tmp_reg = FIELD_PREP(MCP3464_CONFIG3_CONV_MODE_MASK,
                             MCP3464_CONFIG3_CONV_MODE_ONE_SHOT_STANDBY);
        tmp_reg |= FIELD_PREP(MCP3464_CONFIG3_DATA_FORMAT_MASK,
                              MCP3464_CONFIG3_DATA_FMT_32B_SGN_EXT);
        tmp_reg |= MCP3464_CONFIG3_EN_OFFCAL_MASK;
        tmp_reg |= MCP3464_CONFIG3_EN_GAINCAL_MASK;

        ret = mcp3564_write_8bits(adc, MCP3564_CONFIG3_REG, tmp_reg);
        if (ret)
                return ret;

        tmp_reg = FIELD_PREP(MCP3564_CONFIG2_BOOST_CURRENT_MASK, MCP3564_BOOST_CURRENT_x1_00);
        tmp_reg |= FIELD_PREP(MCP3564_CONFIG2_HARDWARE_GAIN_MASK, 0x01);
        tmp_reg |= FIELD_PREP(MCP3564_CONFIG2_AZ_MUX_MASK, 1);

        ret = mcp3564_write_8bits(adc, MCP3564_CONFIG2_REG, tmp_reg);
        if (ret)
                return ret;

        adc->hwgain = 0x01;
        adc->auto_zeroing_mux = true;
        adc->auto_zeroing_ref = false;
        adc->current_boost_mode = MCP3564_BOOST_CURRENT_x1_00;

        tmp_reg = FIELD_PREP(MCP3564_CONFIG1_OVERSPL_RATIO_MASK, MCP3564_OVERSAMPLING_RATIO_98304);

        ret = mcp3564_write_8bits(adc, MCP3564_CONFIG1_REG, tmp_reg);
        if (ret)
                return ret;

        adc->oversampling = MCP3564_OVERSAMPLING_RATIO_98304;

        tmp_reg = FIELD_PREP(MCP3564_CONFIG0_ADC_MODE_MASK, MCP3564_ADC_MODE_STANDBY);
        tmp_reg |= FIELD_PREP(MCP3564_CONFIG0_CS_SEL_MASK, MCP3564_CONFIG0_CS_SEL_0_0_uA);
        tmp_reg |= FIELD_PREP(MCP3564_CONFIG0_CLK_SEL_MASK, MCP3564_CONFIG0_USE_INT_CLK);
        tmp_reg |= MCP3456_CONFIG0_BIT6_DEFAULT;

        if (internal_vref)
                tmp_reg |= FIELD_PREP(MCP3456_CONFIG0_VREF_MASK, 1);

        ret = mcp3564_write_8bits(adc, MCP3564_CONFIG0_REG, tmp_reg);

        adc->burnout_mode = MCP3564_CONFIG0_CS_SEL_0_0_uA;

        return ret;
}

static IIO_DEVICE_ATTR(auto_zeroing_ref_enable, 0644,
                       mcp3564_auto_zeroing_ref_show,
                       mcp3564_auto_zeroing_ref_store, 0);

static IIO_DEVICE_ATTR(auto_zeroing_mux_enable, 0644,
                       mcp3564_auto_zeroing_mux_show,
                       mcp3564_auto_zeroing_mux_store, 0);

static struct attribute *mcp3564_attributes[] = {
        &iio_dev_attr_auto_zeroing_mux_enable.dev_attr.attr,
        NULL
};

static struct attribute *mcp3564r_attributes[] = {
        &iio_dev_attr_auto_zeroing_mux_enable.dev_attr.attr,
        &iio_dev_attr_auto_zeroing_ref_enable.dev_attr.attr,
        NULL
};

static struct attribute_group mcp3564_attribute_group = {
        .attrs = mcp3564_attributes,
};

static struct attribute_group mcp3564r_attribute_group = {
        .attrs = mcp3564r_attributes,
};

static const struct iio_info mcp3564_info = {
        .read_raw = mcp3564_read_raw,
        .read_avail = mcp3564_read_avail,
        .write_raw = mcp3564_write_raw,
        .write_raw_get_fmt = mcp3564_write_raw_get_fmt,
        .read_label = mcp3564_read_label,
        .attrs = &mcp3564_attribute_group,
};

static const struct iio_info mcp3564r_info = {
        .read_raw = mcp3564_read_raw,
        .read_avail = mcp3564_read_avail,
        .write_raw = mcp3564_write_raw,
        .write_raw_get_fmt = mcp3564_write_raw_get_fmt,
        .read_label = mcp3564_read_label,
        .attrs = &mcp3564r_attribute_group,
};

static int mcp3564_probe(struct spi_device *spi)
{
        int ret;
        struct iio_dev *indio_dev;
        struct mcp3564_state *adc;
        bool use_internal_vref_attr;

        indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc));
        if (!indio_dev)
                return -ENOMEM;

        adc = iio_priv(indio_dev);
        adc->spi = spi;

        dev_dbg(&spi->dev, "%s: probe(spi = 0x%p)\n", __func__, spi);

        /*
         * Do any chip specific initialization, e.g:
         * read/write some registers
         * enable/disable certain channels
         * change the sampling rate to the requested value
         */
        ret = mcp3564_config(indio_dev, &use_internal_vref_attr);
        if (ret)
                return dev_err_probe(&spi->dev, ret,
                                     "Can't configure MCP356X device\n");

        dev_dbg(&spi->dev, "%s: Vref (mV): %d\n", __func__, adc->vref_mv);

        mcp3564_fill_scale_tbls(adc);

        indio_dev->name = adc->chip_info->name;
        indio_dev->modes = INDIO_DIRECT_MODE;

        if (use_internal_vref_attr)
                indio_dev->info = &mcp3564r_info;
        else
                indio_dev->info = &mcp3564_info;

        mutex_init(&adc->lock);

        ret = devm_iio_device_register(&spi->dev, indio_dev);
        if (ret)
                return dev_err_probe(&spi->dev, ret,
                                     "Can't register IIO device\n");

        return 0;
}

static const struct of_device_id mcp3564_dt_ids[] = {
        { .compatible = "microchip,mcp3461", .data = &mcp3564_chip_infos_tbl[mcp3461] },
        { .compatible = "microchip,mcp3462", .data = &mcp3564_chip_infos_tbl[mcp3462] },
        { .compatible = "microchip,mcp3464", .data = &mcp3564_chip_infos_tbl[mcp3464] },
        { .compatible = "microchip,mcp3561", .data = &mcp3564_chip_infos_tbl[mcp3561] },
        { .compatible = "microchip,mcp3562", .data = &mcp3564_chip_infos_tbl[mcp3562] },
        { .compatible = "microchip,mcp3564", .data = &mcp3564_chip_infos_tbl[mcp3564] },
        { .compatible = "microchip,mcp3461r", .data = &mcp3564_chip_infos_tbl[mcp3461r] },
        { .compatible = "microchip,mcp3462r", .data = &mcp3564_chip_infos_tbl[mcp3462r] },
        { .compatible = "microchip,mcp3464r", .data = &mcp3564_chip_infos_tbl[mcp3464r] },
        { .compatible = "microchip,mcp3561r", .data = &mcp3564_chip_infos_tbl[mcp3561r] },
        { .compatible = "microchip,mcp3562r", .data = &mcp3564_chip_infos_tbl[mcp3562r] },
        { .compatible = "microchip,mcp3564r", .data = &mcp3564_chip_infos_tbl[mcp3564r] },
        { }
};
MODULE_DEVICE_TABLE(of, mcp3564_dt_ids);

static const struct spi_device_id mcp3564_id[] = {
        { "mcp3461", (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3461] },
        { "mcp3462", (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3462] },
        { "mcp3464", (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3464] },
        { "mcp3561", (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3561] },
        { "mcp3562", (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3562] },
        { "mcp3564", (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3564] },
        { "mcp3461r", (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3461r] },
        { "mcp3462r", (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3462r] },
        { "mcp3464r", (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3464r] },
        { "mcp3561r", (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3561r] },
        { "mcp3562r", (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3562r] },
        { "mcp3564r", (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3564r] },
        { }
};
MODULE_DEVICE_TABLE(spi, mcp3564_id);

static struct spi_driver mcp3564_driver = {
        .driver = {
                .name = "mcp3564",
                .of_match_table = mcp3564_dt_ids,
        },
        .probe = mcp3564_probe,
        .id_table = mcp3564_id,
};

module_spi_driver(mcp3564_driver);

MODULE_AUTHOR("Marius Cristea <marius.cristea@microchip.com>");
MODULE_DESCRIPTION("Microchip MCP346x/MCP346xR and MCP356x/MCP356xR ADCs");
MODULE_LICENSE("GPL v2");