linux/drivers/iio/adc/mcp3564.c
David Lechner 8293a60cb5 iio: adc: mcp3564: use devm_regulator_get_enable_read_voltage()
This makes use of the new devm_regulator_get_enable_read_voltage()
helper function to reduce boilerplate code in the MCP3564 ADC driver.

The error message is slightly changed since there are fewer error
return paths.

Setting adc->vref_mv is consolidated into a single place to make the
logic easier to follow.

A use_internal_vref_attr local variable is added to make it more
obvious what the difference between the two iio info structures is.

The return value of the "Unknown Vref" dev_err_probe() is hard-coded to
-ENODEV instead of ret since it was getting a bit far from where ret
was set and logically that is the only value it could have.

Signed-off-by: David Lechner <dlechner@baylibre.com>
Reviewed-by: Marius Cristea <marius.cristea@microchip.com>
Link: https://patch.msgid.link/20240723-iio-regulator-refactor-round-3-v2-1-ae9291201785@baylibre.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
2024-08-03 10:13:43 +01:00

1484 lines
41 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 sprintf(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 dev_err_probe(dev, -ENOMEM, "Can't allocate memory\n");
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");