mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 20:22:09 +00:00
8293a60cb5
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>
1484 lines
41 KiB
C
1484 lines
41 KiB
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 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");
|