You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1315 lines
42 KiB
1315 lines
42 KiB
/** |
|
* Copyright (c) 2024 Syslinbit SCOP SAS |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
/** |
|
* @file |
|
* @brief ADC driver for the MCP3561R/2R/4R ADCs. |
|
*/ |
|
|
|
#define DT_DRV_COMPAT microchip_mcp356xr |
|
|
|
#include <zephyr/drivers/spi.h> |
|
#include <zephyr/drivers/gpio.h> |
|
#include <zephyr/drivers/adc.h> |
|
#include <zephyr/drivers/adc/mcp356xr.h> |
|
#include <zephyr/logging/log.h> |
|
#include <zephyr/device.h> |
|
#include <zephyr/kernel.h> |
|
#include <zephyr/sys/util.h> |
|
#include <zephyr/sys/util_macro.h> |
|
#include <zephyr/sys/byteorder.h> |
|
|
|
#ifdef CONFIG_ADC_MCP356XR_USE_READ_CRC |
|
#include <zephyr/sys/crc.h> |
|
#endif |
|
|
|
#include <stdint.h> |
|
#include <stdbool.h> |
|
#include <stddef.h> |
|
|
|
LOG_MODULE_REGISTER(adc_mcp356xr, CONFIG_ADC_LOG_LEVEL); |
|
|
|
#define ADC_CONTEXT_USES_KERNEL_TIMER |
|
#include "adc_context.h" |
|
|
|
#define MCP356XR_CRC_POLY (0x8005) |
|
#define MCP356XR_CRC_INIT (0) |
|
|
|
#define MCP356XR_COMMAND_FIELD_DEVICE_ADDRESS_MASK GENMASK(7, 6) |
|
#define MCP356XR_COMMAND_FIELD_REGISTER_ADDRESS_MASK GENMASK(5, 2) |
|
#define MCP356XR_COMMAND_FIELD_FAST_COMMAND_MASK GENMASK(5, 2) |
|
#define MCP356XR_COMMAND_FIELD_COMMAND_TYPE_MASK GENMASK(1, 0) |
|
|
|
enum adc_mcp356xr_command_type { |
|
MCP356XR_COMMAND_TYPE_FAST = 0x0, |
|
MCP356XR_COMMAND_TYPE_SIMPLE_READ = 0x1, |
|
MCP356XR_COMMAND_TYPE_INCREMENTAL_WRITE = 0x2, |
|
MCP356XR_COMMAND_TYPE_INCREMENTAL_READ = 0x3, |
|
}; |
|
|
|
enum adc_mcp356xr_fast_command { |
|
MCP356XR_FAST_COMMAND_START_CONVERSION = 0xA, |
|
MCP356XR_FAST_COMMAND_ENTER_STANDBY = 0xB, |
|
MCP356XR_FAST_COMMAND_SHUTDOWN = 0xC, |
|
MCP356XR_FAST_COMMAND_FULL_SHUTDOWN = 0xD, |
|
MCP356XR_FAST_COMMAND_RESET = 0xE, |
|
}; |
|
|
|
enum adc_mcp356xr_register_address { |
|
MCP356XR_REGISTER_ADDRESS_ADCDATA = 0x0, |
|
MCP356XR_REGISTER_ADDRESS_CONFIG0 = 0x1, |
|
MCP356XR_REGISTER_ADDRESS_CONFIG1 = 0x2, |
|
MCP356XR_REGISTER_ADDRESS_CONFIG2 = 0x3, |
|
MCP356XR_REGISTER_ADDRESS_CONFIG3 = 0x4, |
|
MCP356XR_REGISTER_ADDRESS_IRQ = 0x5, |
|
MCP356XR_REGISTER_ADDRESS_MUX = 0x6, |
|
MCP356XR_REGISTER_ADDRESS_SCAN = 0x7, |
|
MCP356XR_REGISTER_ADDRESS_TIMER = 0x8, |
|
MCP356XR_REGISTER_ADDRESS_OFFSETCAL = 0x9, |
|
MCP356XR_REGISTER_ADDRESS_GAINCAL = 0xA, |
|
MCP356XR_REGISTER_ADDRESS_LOCK = 0xD, |
|
MCP356XR_REGISTER_ADDRESS_CRCCFG = 0xF, |
|
}; |
|
|
|
struct adc_mcp356xr_command { |
|
enum adc_mcp356xr_command_type type; |
|
union { |
|
enum adc_mcp356xr_fast_command fast_command; |
|
enum adc_mcp356xr_register_address register_address; |
|
}; |
|
}; |
|
|
|
#define MCP356XR_STATUS_FIELD_ADDRESS_ACK_MASK GENMASK(5, 3) |
|
#define MCP356XR_STATUS_FIELD_FLAGS_MASK GENMASK(2, 0) |
|
|
|
#define MCP356XR_STATUS_FIELD_DR_STATUS_MASK GENMASK(2, 2) |
|
#define MCP356XR_STATUS_FIELD_CRCCFG_STATUS_MASK GENMASK(1, 1) |
|
#define MCP356XR_STATUS_FIELD_POR_STATUS_MASK GENMASK(0, 0) |
|
|
|
#define MCP356XR_STATUS_ADDRESS_ACK_FIELD_VALUE(address) ((address << 1) | (~address & 0x1)) |
|
|
|
#define MCP356XR_REGISTER_FIELD_CONFIG0_VREF_SEL_MASK GENMASK(7, 7) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG0_CONFIG0_MASK GENMASK(6, 6) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG0_CLK_SEL_MASK GENMASK(5, 4) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG0_CS_SEL_MASK GENMASK(3, 2) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG0_ADC_MODE_MASK GENMASK(1, 0) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG1_PRE_MASK GENMASK(7, 6) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG1_OSR_MASK GENMASK(5, 2) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG2_BOOST_MASK GENMASK(7, 6) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG2_GAIN_MASK GENMASK(5, 3) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG2_AZ_MUX_MASK GENMASK(2, 2) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG2_AZ_REF_MASK GENMASK(1, 1) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG3_CONV_MODE_MASK GENMASK(7, 6) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG3_DATA_FORMAT_MASK GENMASK(5, 4) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG3_CRC_FORMAT_MASK GENMASK(3, 3) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG3_EN_CRCCOM_MASK GENMASK(2, 2) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG3_EN_OFFCAL_MASK GENMASK(1, 1) |
|
#define MCP356XR_REGISTER_FIELD_CONFIG3_EN_GAINCAL_MASK GENMASK(0, 0) |
|
#define MCP356XR_REGISTER_FIELD_IRQ_DR_STATUS_MASK GENMASK(6, 6) |
|
#define MCP356XR_REGISTER_FIELD_IRQ_CRCCFG_STATUS_MASK GENMASK(5, 5) |
|
#define MCP356XR_REGISTER_FIELD_IRQ_POR_STATUS_MASK GENMASK(4, 4) |
|
#define MCP356XR_REGISTER_FIELD_IRQ_IRQ_MODE_MASK GENMASK(3, 2) |
|
#define MCP356XR_REGISTER_FIELD_IRQ_EN_FASTCMD_MASK GENMASK(1, 1) |
|
#define MCP356XR_REGISTER_FIELD_IRQ_EN_STP_MASK GENMASK(0, 0) |
|
#define MCP356XR_REGISTER_FIELD_MUX_MUX_VIN_POSITIVE_MASK GENMASK(7, 4) |
|
#define MCP356XR_REGISTER_FIELD_MUX_MUX_VIN_NEGATIVE_MASK GENMASK(3, 0) |
|
#define MCP356XR_REGISTER_FIELD_SCAN_DLY_MASK GENMASK(23, 21) |
|
#define MCP356XR_REGISTER_FIELD_SCAN_CHANNEL_SELECTION_MASK GENMASK(15, 0) |
|
#define MCP356XR_REGISTER_FIELD_TIMER_TIMER_MASK GENMASK(23, 0) |
|
#define MCP356XR_REGISTER_FIELD_OFFSETCAL_OFFSETCAL_MASK GENMASK(23, 0) |
|
#define MCP356XR_REGISTER_FIELD_GAINCAL_GAINCAL_MASK GENMASK(23, 0) |
|
#define MCP356XR_REGISTER_FIELD_LOCK_LOCK_MASK GENMASK(7, 0) |
|
#define MCP356XR_REGISTER_FIELD_CRCCFG_CRCCFG_MASK GENMASK(15, 0) |
|
|
|
enum { |
|
MCP356XR_REGISTER_CONFIG0_VREF_SEL_EXTERNAL_VOLTAGE_REF = 0, |
|
MCP356XR_REGISTER_CONFIG0_VREF_SEL_INTERNAL_VOLTAGE_REF = 1, |
|
}; |
|
|
|
enum { |
|
MCP356XR_REGISTER_CONFIG0_CONFIG0_DO_NOT_ENTER_PARTIAL_SHUTDOWN = 0, |
|
MCP356XR_REGISTER_CONFIG0_CONFIG0_ENTER_PARTIAL_SHUTDOWN = 1, |
|
}; |
|
|
|
enum { |
|
MCP356XR_REGISTER_CONFIG0_CLK_SEL_EXTERNAL_CLOCK = 0, |
|
MCP356XR_REGISTER_CONFIG0_CLK_SEL_EXTERNAL_CLOCK_ = 1, /* also selects the external clock */ |
|
MCP356XR_REGISTER_CONFIG0_CLK_SEL_INTERNAL_CLOCK_NO_OUTPUT = 2, |
|
MCP356XR_REGISTER_CONFIG0_CLK_SEL_INTERNAL_CLOCK_OUTPUT_AMCLK = 3, |
|
}; |
|
|
|
enum { |
|
MCP356XR_REGISTER_CONFIG0_CS_SEL_NO_CURRENT = 0, |
|
MCP356XR_REGISTER_CONFIG0_CS_SEL_0uA9 = 1, |
|
MCP356XR_REGISTER_CONFIG0_CS_SEL_3uA7 = 2, |
|
MCP356XR_REGISTER_CONFIG0_CS_SEL_15uA = 3, |
|
}; |
|
|
|
enum { |
|
MCP356XR_REGISTER_CONFIG0_ADC_MODE_SHUTDOWN = 0, |
|
MCP356XR_REGISTER_CONFIG0_ADC_MODE_SHUTDOWN_ = 1, /* also causes ADC to shutdown */ |
|
MCP356XR_REGISTER_CONFIG0_ADC_MODE_STANDBY = 2, |
|
MCP356XR_REGISTER_CONFIG0_ADC_MODE_CONVERSION = 3, |
|
}; |
|
|
|
enum { |
|
MCP356XR_REGISTER_CONFIG1_OSR_32 = 0, |
|
MCP356XR_REGISTER_CONFIG1_OSR_64 = 1, |
|
MCP356XR_REGISTER_CONFIG1_OSR_128 = 2, |
|
MCP356XR_REGISTER_CONFIG1_OSR_256 = 3, |
|
MCP356XR_REGISTER_CONFIG1_OSR_512 = 4, |
|
MCP356XR_REGISTER_CONFIG1_OSR_1024 = 5, |
|
MCP356XR_REGISTER_CONFIG1_OSR_2048 = 6, |
|
MCP356XR_REGISTER_CONFIG1_OSR_4096 = 7, |
|
MCP356XR_REGISTER_CONFIG1_OSR_8192 = 8, |
|
MCP356XR_REGISTER_CONFIG1_OSR_16384 = 9, |
|
MCP356XR_REGISTER_CONFIG1_OSR_20480 = 10, |
|
MCP356XR_REGISTER_CONFIG1_OSR_24576 = 11, |
|
MCP356XR_REGISTER_CONFIG1_OSR_40960 = 12, |
|
MCP356XR_REGISTER_CONFIG1_OSR_49152 = 13, |
|
MCP356XR_REGISTER_CONFIG1_OSR_81920 = 14, |
|
MCP356XR_REGISTER_CONFIG1_OSR_98304 = 15, |
|
}; |
|
|
|
#define MCP356XR_REGISTER_CONFIG1_OSR(oversampling) \ |
|
((oversampling <= 14) ? (oversampling - 5) : MCP356XR_REGISTER_CONFIG1_OSR_16384) |
|
|
|
enum { |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_MUL_16_DIGITAL_MUL_4 = 7, |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_MUL_16_DIGITAL_MUL_2 = 6, |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_MUL_16 = 5, |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_MUL_8 = 4, |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_MUL_4 = 3, |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_MUL_2 = 2, |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_MUL_1 = 1, |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_DIV_3 = 0, |
|
}; |
|
|
|
enum { |
|
MCP356XR_REGISTER_CONFIG3_CONV_MODE_ONE_SHOT_GO_SHUTDOWN = 0, |
|
MCP356XR_REGISTER_CONFIG3_CONV_MODE_ONE_SHOT_GO_SHUTDOWN_BIS = 1, |
|
MCP356XR_REGISTER_CONFIG3_CONV_MODE_ONE_SHOT_GO_STANDBY = 2, |
|
MCP356XR_REGISTER_CONFIG3_CONV_MODE_CONTINUOUS = 3, |
|
}; |
|
|
|
enum { |
|
MCP356XR_REGISTER_CONFIG3_DATA_FORMAT_1_BIT_SIGN_23_BITS_DATA = 0, |
|
MCP356XR_REGISTER_CONFIG3_DATA_FORMAT_1_BIT_SIGN_23_BITS_DATA_8_BITS_PADDING = 1, |
|
MCP356XR_REGISTER_CONFIG3_DATA_FORMAT_8_BITS_SIGN_24_BITS_DATA = 2, |
|
MCP356XR_REGISTER_CONFIG3_DATA_FORMAT_4_BITS_CHAN_ID_4_BITS_SIGN_24_BITS_DATA = 3, |
|
}; |
|
|
|
enum { |
|
MCP356XR_REGISTER_CONFIG3_CRC_FORMAT_16_BITS_CRC = 0, |
|
MCP356XR_REGISTER_CONFIG3_CRC_FORMAT_16_BITS_CRC_16_BITS_PADDING = 1, |
|
}; |
|
|
|
enum { |
|
MCP356XR_REGISTER_IRQ_IRQ_MODE_IRQ_OUTPUT_OPEN_DRAIN = 0, |
|
MCP356XR_REGISTER_IRQ_IRQ_MODE_IRQ_OUTPUT_PUSH_PULL = 1, |
|
MCP356XR_REGISTER_IRQ_IRQ_MODE_MDAT_OUTPUT = 2, |
|
MCP356XR_REGISTER_IRQ_IRQ_MODE_MDAT_OUTPUT_BIS = 3, |
|
}; |
|
|
|
enum { |
|
MCP356XR_REGISTER_CONFIG_FLAG_ENABLED = 1, |
|
MCP356XR_REGISTER_CONFIG_FLAG_DISABLED = 0, |
|
}; |
|
|
|
enum { |
|
MCP356XR_REGISTER_STATUS_FLAG_ENABLED = 0, |
|
MCP356XR_REGISTER_STATUS_FLAG_DISABLED = 1, |
|
}; |
|
|
|
#define MCP356XR_REGISTER_LOCK_UNLOCK_REGISTERS_CODE 0xA5 |
|
|
|
#define MCP356XR_FIELD_PREP(name, value) FIELD_PREP(MCP356XR_##name##_MASK, value) |
|
#define MCP356XR_FIELD_GET(name, value) FIELD_GET(MCP356XR_##name##_MASK, value) |
|
|
|
#define MCP356XR_COMMAND_FIELD_PREP(field, value) MCP356XR_FIELD_PREP(COMMAND_FIELD_##field, value) |
|
|
|
#define MCP356XR_STATUS_FIELD_GET(field, value) MCP356XR_FIELD_GET(STATUS_FIELD_##field, value) |
|
|
|
#define MCP356XR_REGISTER_FIELD_PREP(register, field, value) \ |
|
MCP356XR_FIELD_PREP(REGISTER_FIELD_##register##_##field, value) |
|
#define MCP356XR_REGISTER_FIELD_GET(register, field, value) \ |
|
MCP356XR_FIELD_GET(REGISTER_FIELD_##register##_##field, value) |
|
#define MCP356XR_REGISTER_FIELD_SET(register, field, holder, value) \ |
|
(holder) = ((holder) & ~(MCP356XR_REGISTER_FIELD_##register##_##field##_MASK)) | \ |
|
MCP356XR_REGISTER_FIELD_PREP(register, field, value) |
|
|
|
#define REGISTER_ADDRESS(register_name) MCP356XR_REGISTER_ADDRESS_##register_name |
|
|
|
#define REGISTER_INDEX_(register_address) \ |
|
((register_address >= MCP356XR_REGISTER_ADDRESS_CONFIG0) \ |
|
? ((register_address <= MCP356XR_REGISTER_ADDRESS_MUX) \ |
|
? (register_address - MCP356XR_REGISTER_ADDRESS_CONFIG0) \ |
|
: MCP356XR_REGISTER_ADDRESS_MUX) \ |
|
: MCP356XR_REGISTER_ADDRESS_MUX) |
|
#define REGISTER_INDEX(register_name) REGISTER_INDEX_(REGISTER_ADDRESS(register_name)) |
|
|
|
#define REGISTER_ARRAY_SIZE (REGISTER_INDEX(MUX) + 1) |
|
|
|
struct adc_mcp356xr_config { |
|
struct spi_dt_spec spi; |
|
#ifndef CONFIG_ADC_MCP356XR_POLL |
|
struct gpio_dt_spec irq; |
|
#endif |
|
uint8_t address; |
|
k_timeout_t vref_settle_time; |
|
uint8_t init_reg_value[REGISTER_ARRAY_SIZE]; |
|
}; |
|
|
|
struct channel_registers { |
|
uint8_t config0; |
|
uint8_t config2; |
|
uint8_t mux; |
|
}; |
|
|
|
struct adc_mcp356xr_data { |
|
const struct device *dev; |
|
struct adc_context ctx; |
|
int32_t *buffer; |
|
int32_t *repeat_buffer; |
|
uint32_t channels; |
|
|
|
bool wait_for_vref_stabilization; |
|
bool internal_vref_in_use; |
|
|
|
uint8_t config1_register; |
|
struct k_mutex channel_registers_mutex; |
|
struct channel_registers channel_registers[CONFIG_ADC_MCP356XR_ADC_CHANNEL_COUNT]; |
|
|
|
struct k_thread thread; |
|
struct k_sem start_sequence; |
|
|
|
#ifndef CONFIG_ADC_MCP356XR_POLL |
|
struct gpio_callback irq_callback_data; |
|
struct k_sem irq_occurred; |
|
#endif |
|
|
|
K_KERNEL_STACK_MEMBER(stack, CONFIG_ADC_MCP356XR_THREAD_STACK_SIZE); |
|
}; |
|
|
|
#ifndef CONFIG_ADC_MCP356XR_POLL |
|
static void adc_mcp356xr_irq(const struct device *gpio_port, struct gpio_callback *callback_data, |
|
gpio_port_pins_t gpio_pins) |
|
{ |
|
ARG_UNUSED(gpio_port); |
|
ARG_UNUSED(gpio_pins); |
|
|
|
struct adc_mcp356xr_data *data = |
|
CONTAINER_OF(callback_data, struct adc_mcp356xr_data, irq_callback_data); |
|
|
|
k_sem_give(&(data->irq_occurred)); |
|
} |
|
|
|
static int adc_mcp356xr_irq_init(const struct device *dev) |
|
{ |
|
const struct adc_mcp356xr_config *config = dev->config; |
|
struct adc_mcp356xr_data *data = dev->data; |
|
|
|
int err = 0; |
|
|
|
k_sem_init(&(data->irq_occurred), 0, 1); |
|
|
|
if (!gpio_is_ready_dt(&(config->irq))) { |
|
LOG_ERR("IRQ GPIO device not ready"); |
|
return -ENODEV; |
|
} |
|
|
|
gpio_init_callback(&(data->irq_callback_data), adc_mcp356xr_irq, BIT(config->irq.pin)); |
|
err = gpio_add_callback_dt(&(config->irq), &(data->irq_callback_data)); |
|
if (err) { |
|
LOG_ERR("Failed to add irq callback (err %d)", err); |
|
return err; |
|
} |
|
|
|
err = gpio_pin_configure_dt(&(config->irq), GPIO_INPUT); |
|
if (err) { |
|
LOG_ERR("Cannot configure IRQ GPIO (err %d)", err); |
|
return err; |
|
} |
|
|
|
err = gpio_pin_interrupt_configure_dt(&(config->irq), GPIO_INT_EDGE_TO_ACTIVE); |
|
if (err) { |
|
LOG_ERR("Failed to enable interrupt on IRQ pin (err %d)", err); |
|
return err; |
|
} |
|
|
|
return 0; |
|
} |
|
#endif |
|
|
|
static int adc_mcp356xr_transceive(const struct device *dev, uint8_t command_byte, |
|
uint8_t *status_byte, uint8_t *tx_data, size_t tx_length, |
|
uint8_t *rx_data, size_t rx_length, uint16_t *crc) |
|
{ |
|
const struct adc_mcp356xr_config *config = dev->config; |
|
|
|
const struct spi_buf tx_buf[2] = { |
|
{ |
|
.buf = &command_byte, |
|
.len = 1, |
|
}, |
|
{ |
|
.buf = tx_data, |
|
.len = tx_length, |
|
}, |
|
}; |
|
|
|
const struct spi_buf_set tx = {.buffers = tx_buf, .count = ARRAY_SIZE(tx_buf)}; |
|
|
|
#ifdef CONFIG_ADC_MCP356XR_USE_READ_CRC |
|
uint8_t crc_buffer[2] = {0}; |
|
|
|
const struct spi_buf rx_buf[3] = { |
|
{ |
|
.buf = status_byte, |
|
.len = 1, |
|
}, |
|
{ |
|
.buf = rx_data, |
|
.len = rx_length, |
|
}, |
|
{ |
|
.buf = crc_buffer, |
|
.len = 2, |
|
}, |
|
}; |
|
|
|
const struct spi_buf_set rx = { |
|
.buffers = rx_buf, |
|
.count = ((crc != NULL) ? ARRAY_SIZE(rx_buf) : (ARRAY_SIZE(rx_buf) - 1)), |
|
}; |
|
#else |
|
const struct spi_buf rx_buf[2] = { |
|
{ |
|
.buf = status_byte, |
|
.len = 1, |
|
}, |
|
{ |
|
.buf = rx_data, |
|
.len = rx_length, |
|
}, |
|
}; |
|
|
|
const struct spi_buf_set rx = {.buffers = rx_buf, .count = ARRAY_SIZE(rx_buf)}; |
|
#endif |
|
|
|
int err = 0; |
|
|
|
err = spi_transceive_dt(&(config->spi), &tx, &rx); |
|
if (err) { |
|
LOG_ERR("SPI error occurred while communicating with ADC (error = %d)", err); |
|
return -EIO; |
|
} |
|
|
|
#ifdef CONFIG_ADC_MCP356XR_USE_READ_CRC |
|
if (crc != NULL) { |
|
*crc = sys_get_be16(crc_buffer); |
|
} |
|
#else |
|
(void)crc; |
|
#endif |
|
|
|
return 0; |
|
} |
|
|
|
static int adc_mcp356xr_send_command(const struct device *dev, struct adc_mcp356xr_command command, |
|
uint8_t *status_flags, uint8_t *data, size_t length) |
|
{ |
|
const struct adc_mcp356xr_config *config = dev->config; |
|
|
|
int err = 0; |
|
uint8_t status_byte = 0; |
|
uint16_t crc = 0; |
|
|
|
uint8_t command_byte = MCP356XR_COMMAND_FIELD_PREP(DEVICE_ADDRESS, config->address) | |
|
MCP356XR_COMMAND_FIELD_PREP(COMMAND_TYPE, command.type); |
|
|
|
if (command.type == MCP356XR_COMMAND_TYPE_FAST) { |
|
command_byte |= MCP356XR_COMMAND_FIELD_PREP(FAST_COMMAND, command.fast_command); |
|
} else { |
|
command_byte |= |
|
MCP356XR_COMMAND_FIELD_PREP(REGISTER_ADDRESS, command.register_address); |
|
} |
|
|
|
switch (command.type) { |
|
case MCP356XR_COMMAND_TYPE_FAST: |
|
err = adc_mcp356xr_transceive(dev, command_byte, &status_byte, NULL, 0, NULL, 0, |
|
NULL); |
|
break; |
|
case MCP356XR_COMMAND_TYPE_SIMPLE_READ: |
|
err = adc_mcp356xr_transceive(dev, command_byte, &status_byte, NULL, 0, data, |
|
length, &crc); |
|
break; |
|
case MCP356XR_COMMAND_TYPE_INCREMENTAL_WRITE: |
|
err = adc_mcp356xr_transceive(dev, command_byte, &status_byte, data, length, NULL, |
|
0, NULL); |
|
break; |
|
case MCP356XR_COMMAND_TYPE_INCREMENTAL_READ: |
|
err = adc_mcp356xr_transceive(dev, command_byte, &status_byte, NULL, 0, data, |
|
length, &crc); |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
if (err) { |
|
return err; |
|
} |
|
|
|
if (status_flags != NULL) { |
|
*status_flags = MCP356XR_STATUS_FIELD_GET(FLAGS, status_byte); |
|
} |
|
|
|
#ifdef CONFIG_ADC_MCP356XR_USE_READ_CRC |
|
if ((command.type == MCP356XR_COMMAND_TYPE_SIMPLE_READ) || |
|
(command.type == MCP356XR_COMMAND_TYPE_INCREMENTAL_READ)) { |
|
uint16_t computed_crc = 0; |
|
|
|
computed_crc = crc16(MCP356XR_CRC_POLY, MCP356XR_CRC_INIT, &status_byte, 1); |
|
computed_crc = crc16(MCP356XR_CRC_POLY, computed_crc, data, length); |
|
|
|
if (crc != computed_crc) { |
|
return -EILSEQ; |
|
} |
|
} |
|
#endif |
|
|
|
if (MCP356XR_STATUS_FIELD_GET(ADDRESS_ACK, status_byte) != |
|
MCP356XR_STATUS_ADDRESS_ACK_FIELD_VALUE(config->address)) { |
|
return -ENOMSG; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int adc_mcp356xr_send_fast_command(const struct device *dev, |
|
enum adc_mcp356xr_fast_command fast_command) |
|
{ |
|
struct adc_mcp356xr_command command = { |
|
.type = MCP356XR_COMMAND_TYPE_FAST, |
|
.fast_command = fast_command, |
|
}; |
|
|
|
return adc_mcp356xr_send_command(dev, command, NULL, NULL, 0); |
|
} |
|
|
|
static int adc_mcp356xr_incremental_write(const struct device *dev, |
|
enum adc_mcp356xr_register_address start_register_address, |
|
uint8_t *data, size_t length) |
|
{ |
|
struct adc_mcp356xr_command command = { |
|
.type = MCP356XR_COMMAND_TYPE_INCREMENTAL_WRITE, |
|
.register_address = start_register_address, |
|
}; |
|
|
|
return adc_mcp356xr_send_command(dev, command, NULL, data, length); |
|
} |
|
|
|
static int adc_mcp356xr_simple_read_with_status(const struct device *dev, |
|
enum adc_mcp356xr_register_address register_address, |
|
uint8_t *data, size_t length, uint8_t *status) |
|
{ |
|
struct adc_mcp356xr_command command = { |
|
.type = MCP356XR_COMMAND_TYPE_SIMPLE_READ, |
|
.register_address = register_address, |
|
}; |
|
|
|
return adc_mcp356xr_send_command(dev, command, status, data, length); |
|
} |
|
|
|
static inline int adc_mcp356xr_simple_read(const struct device *dev, |
|
enum adc_mcp356xr_register_address register_address, |
|
uint8_t *data, size_t length) |
|
{ |
|
return adc_mcp356xr_simple_read_with_status(dev, register_address, data, length, NULL); |
|
} |
|
|
|
static int adc_mcp356xr_unlock_registers(const struct device *dev) |
|
{ |
|
uint8_t unlock_value = MCP356XR_REGISTER_LOCK_UNLOCK_REGISTERS_CODE; |
|
|
|
return adc_mcp356xr_incremental_write(dev, MCP356XR_REGISTER_ADDRESS_LOCK, &unlock_value, |
|
1); |
|
} |
|
|
|
static inline int adc_mcp356xr_reset(const struct device *dev) |
|
{ |
|
return adc_mcp356xr_send_fast_command(dev, MCP356XR_FAST_COMMAND_RESET); |
|
} |
|
|
|
static inline int adc_mcp356xr_start_conversion(const struct device *dev) |
|
{ |
|
return adc_mcp356xr_send_fast_command(dev, MCP356XR_FAST_COMMAND_START_CONVERSION); |
|
} |
|
|
|
static inline int adc_mcp356xr_get_status(const struct device *dev, bool *data_ready, |
|
bool *por_occurred, bool *crc_error) |
|
{ |
|
uint8_t irq_register_value = 0xFF; |
|
|
|
int err = adc_mcp356xr_simple_read(dev, MCP356XR_REGISTER_ADDRESS_IRQ, &irq_register_value, |
|
1); |
|
|
|
if (data_ready != NULL) { |
|
*data_ready = (MCP356XR_REGISTER_FIELD_GET(IRQ, DR_STATUS, irq_register_value) == |
|
MCP356XR_REGISTER_STATUS_FLAG_ENABLED); |
|
} |
|
|
|
if (crc_error != NULL) { |
|
*crc_error = (MCP356XR_REGISTER_FIELD_GET(IRQ, CRCCFG_STATUS, irq_register_value) == |
|
MCP356XR_REGISTER_STATUS_FLAG_ENABLED); |
|
} |
|
|
|
if (por_occurred != NULL) { |
|
*por_occurred = (MCP356XR_REGISTER_FIELD_GET(IRQ, POR_STATUS, irq_register_value) == |
|
MCP356XR_REGISTER_STATUS_FLAG_ENABLED); |
|
} |
|
|
|
return err; |
|
} |
|
|
|
static inline int adc_mcp356xr_get_data(const struct device *dev, int32_t *data) |
|
{ |
|
int err = 0; |
|
uint8_t status = 0; |
|
uint8_t buffer[4] = {0}; |
|
|
|
uint32_t raw_data = 0; |
|
|
|
__ASSERT_NO_MSG(data != NULL); |
|
|
|
err = adc_mcp356xr_simple_read_with_status(dev, MCP356XR_REGISTER_ADDRESS_ADCDATA, buffer, |
|
4, &status); |
|
if (err) { |
|
return err; |
|
} |
|
|
|
if (MCP356XR_STATUS_FIELD_GET(POR_STATUS, status) == |
|
MCP356XR_REGISTER_STATUS_FLAG_ENABLED) { |
|
/* it seems that a POR occurred so data is probably corrupted */ |
|
return -ENXIO; |
|
} |
|
|
|
raw_data = sys_get_be32(buffer); |
|
|
|
*data = *((int32_t *)&raw_data); |
|
|
|
return 0; |
|
} |
|
|
|
static inline void adc_mcp356xr_channel_registers_init(const struct device *dev, |
|
struct channel_registers *registers) |
|
{ |
|
const struct adc_mcp356xr_config *config = dev->config; |
|
|
|
__ASSERT_NO_MSG(registers != NULL); |
|
|
|
registers->config0 = config->init_reg_value[REGISTER_INDEX(CONFIG0)]; |
|
registers->config2 = config->init_reg_value[REGISTER_INDEX(CONFIG2)]; |
|
registers->mux = config->init_reg_value[REGISTER_INDEX(MUX)]; |
|
} |
|
|
|
static inline void adc_mcp356xr_channel_registers_set(const struct device *dev, uint8_t channel_id, |
|
struct channel_registers registers) |
|
{ |
|
struct adc_mcp356xr_data *data = dev->data; |
|
|
|
__ASSERT_NO_MSG(channel_id < CONFIG_ADC_MCP356XR_ADC_CHANNEL_COUNT); |
|
|
|
k_mutex_lock(&(data->channel_registers_mutex), K_FOREVER); |
|
|
|
data->channel_registers[channel_id].config0 = registers.config0; |
|
data->channel_registers[channel_id].config2 = registers.config2; |
|
data->channel_registers[channel_id].mux = registers.mux; |
|
|
|
k_mutex_unlock(&(data->channel_registers_mutex)); |
|
} |
|
|
|
static inline struct channel_registers adc_mcp356xr_channel_registers_get(const struct device *dev, |
|
uint8_t channel_id) |
|
{ |
|
struct adc_mcp356xr_data *data = dev->data; |
|
struct channel_registers registers; |
|
|
|
__ASSERT_NO_MSG(channel_id < CONFIG_ADC_MCP356XR_ADC_CHANNEL_COUNT); |
|
|
|
k_mutex_lock(&(data->channel_registers_mutex), K_FOREVER); |
|
|
|
registers.config0 = data->channel_registers[channel_id].config0; |
|
registers.config2 = data->channel_registers[channel_id].config2; |
|
registers.mux = data->channel_registers[channel_id].mux; |
|
|
|
k_mutex_unlock(&(data->channel_registers_mutex)); |
|
|
|
return registers; |
|
} |
|
|
|
static inline int adc_mcp356xr_set_gain(struct channel_registers *registers, enum adc_gain gain) |
|
{ |
|
__ASSERT_NO_MSG(registers != NULL); |
|
|
|
switch (gain) { |
|
case ADC_GAIN_1_3: |
|
MCP356XR_REGISTER_FIELD_SET(CONFIG2, GAIN, registers->config2, |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_DIV_3); |
|
break; |
|
case ADC_GAIN_1: |
|
MCP356XR_REGISTER_FIELD_SET(CONFIG2, GAIN, registers->config2, |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_MUL_1); |
|
break; |
|
case ADC_GAIN_2: |
|
MCP356XR_REGISTER_FIELD_SET(CONFIG2, GAIN, registers->config2, |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_MUL_2); |
|
break; |
|
case ADC_GAIN_4: |
|
MCP356XR_REGISTER_FIELD_SET(CONFIG2, GAIN, registers->config2, |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_MUL_4); |
|
break; |
|
case ADC_GAIN_8: |
|
MCP356XR_REGISTER_FIELD_SET(CONFIG2, GAIN, registers->config2, |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_MUL_8); |
|
break; |
|
case ADC_GAIN_16: |
|
MCP356XR_REGISTER_FIELD_SET(CONFIG2, GAIN, registers->config2, |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_MUL_16); |
|
break; |
|
case ADC_GAIN_32: |
|
MCP356XR_REGISTER_FIELD_SET( |
|
CONFIG2, GAIN, registers->config2, |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_MUL_16_DIGITAL_MUL_2); |
|
break; |
|
case ADC_GAIN_64: |
|
MCP356XR_REGISTER_FIELD_SET( |
|
CONFIG2, GAIN, registers->config2, |
|
MCP356XR_REGISTER_CONFIG2_GAIN_ANALOG_MUL_16_DIGITAL_MUL_4); |
|
break; |
|
default: |
|
LOG_ERR("Channel gain '%d' is not supported by device", gain); |
|
return -ENOTSUP; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static inline int adc_mcp356xr_set_inputs(struct channel_registers *registers, |
|
uint8_t positive_input, uint8_t negative_input) |
|
{ |
|
__ASSERT_NO_MSG(registers != NULL); |
|
|
|
if (positive_input == MCP356XR_INPUT_RESERVED_DO_NOT_USE) { |
|
LOG_ERR("Invalid channel positive input %d", positive_input); |
|
return -ENOTSUP; |
|
} |
|
|
|
if (negative_input == MCP356XR_INPUT_RESERVED_DO_NOT_USE) { |
|
LOG_ERR("Invalid channel negative input %d", negative_input); |
|
return -ENOTSUP; |
|
} |
|
|
|
MCP356XR_REGISTER_FIELD_SET(MUX, MUX_VIN_POSITIVE, registers->mux, positive_input); |
|
MCP356XR_REGISTER_FIELD_SET(MUX, MUX_VIN_NEGATIVE, registers->mux, negative_input); |
|
|
|
return 0; |
|
} |
|
|
|
static inline int adc_mcp356xr_set_reference(struct channel_registers *registers, |
|
enum adc_reference reference) |
|
{ |
|
__ASSERT_NO_MSG(registers != NULL); |
|
|
|
if (reference != ADC_REF_EXTERNAL0 && reference != ADC_REF_INTERNAL) { |
|
LOG_ERR("Channel voltage reference '%d' is not supported by device", reference); |
|
return -ENOTSUP; |
|
} |
|
|
|
if (reference == ADC_REF_INTERNAL) { |
|
MCP356XR_REGISTER_FIELD_SET( |
|
CONFIG0, VREF_SEL, registers->config0, |
|
MCP356XR_REGISTER_CONFIG0_VREF_SEL_INTERNAL_VOLTAGE_REF); |
|
} else { |
|
MCP356XR_REGISTER_FIELD_SET( |
|
CONFIG0, VREF_SEL, registers->config0, |
|
MCP356XR_REGISTER_CONFIG0_VREF_SEL_EXTERNAL_VOLTAGE_REF); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static inline int adc_mcp356xr_set_oversampling(const struct device *dev, uint8_t oversampling) |
|
{ |
|
struct adc_mcp356xr_data *data = dev->data; |
|
const struct adc_mcp356xr_config *config = dev->config; |
|
|
|
if (oversampling < 5) { |
|
LOG_ERR("Oversamplings below 2^5 is not supported by device"); |
|
return -ENOTSUP; |
|
} |
|
|
|
if (oversampling > 14) { |
|
LOG_ERR("Oversampling of 2^%d is not supported by device", oversampling); |
|
return -ENOTSUP; |
|
} |
|
|
|
if (oversampling < 8) { |
|
LOG_WRN("Oversamplings below 2^8 does not allow full 24bits resolution. See " |
|
"datasheet DS20006391C, table 5-6, page 46 for more details."); |
|
} |
|
|
|
data->config1_register = config->init_reg_value[REGISTER_INDEX(CONFIG1)]; |
|
MCP356XR_REGISTER_FIELD_SET(CONFIG1, OSR, data->config1_register, |
|
MCP356XR_REGISTER_CONFIG1_OSR(oversampling)); |
|
|
|
return 0; |
|
} |
|
|
|
static int adc_mcp356xr_reset_after_power_on(const struct device *dev) |
|
{ |
|
struct adc_mcp356xr_data *data = dev->data; |
|
|
|
int err = 0; |
|
|
|
err = adc_mcp356xr_unlock_registers(dev); |
|
if (err) { |
|
LOG_ERR("Failed to unlock MCP356x registers (error = %d)", err); |
|
return err; |
|
} |
|
|
|
/* enable fast commands so reset command is taken into account */ |
|
uint8_t irq_register_value = MCP356XR_REGISTER_FIELD_PREP( |
|
IRQ, EN_FASTCMD, MCP356XR_REGISTER_CONFIG_FLAG_ENABLED); |
|
|
|
err = adc_mcp356xr_incremental_write(dev, MCP356XR_REGISTER_ADDRESS_IRQ, |
|
&irq_register_value, 1); |
|
if (err) { |
|
LOG_ERR("Failed to enable fast commands (error = %d)", err); |
|
return err; |
|
} |
|
|
|
err = adc_mcp356xr_reset(dev); |
|
if (err) { |
|
LOG_ERR("Failed to reset MCP356x (error = %d)", err); |
|
return err; |
|
} |
|
|
|
/* by default after a reset the internal voltage reference is selected */ |
|
data->internal_vref_in_use = true; |
|
/* force a wait to ensure voltage is stable before performing the next acquisition */ |
|
data->wait_for_vref_stabilization = true; |
|
|
|
#ifdef CONFIG_ADC_MCP356XR_USE_READ_CRC |
|
const struct adc_mcp356xr_config *config = dev->config; |
|
|
|
/* directly write CONFIG3 register so that CRC on subsequent reads will be enabled */ |
|
uint8_t config3_register_value = config->init_reg_value[REGISTER_INDEX(CONFIG3)]; |
|
|
|
err = adc_mcp356xr_incremental_write(dev, MCP356XR_REGISTER_ADDRESS_CONFIG3, |
|
&config3_register_value, 1); |
|
if (err) { |
|
LOG_ERR("Failed to enable CRC on read communications (error = %d)", err); |
|
return err; |
|
} |
|
#endif |
|
|
|
return 0; |
|
} |
|
|
|
static inline int adc_mcp356xr_get_status_and_sanitize(const struct device *dev, bool *data_ready, |
|
bool *por_occurred, bool *crc_error) |
|
{ |
|
int err = 0; |
|
bool local_por_occurred = false; |
|
|
|
err = adc_mcp356xr_get_status(dev, data_ready, &local_por_occurred, crc_error); |
|
if (local_por_occurred && (err == 0 || err == -EILSEQ)) { |
|
/* Try do reset the ADC if a POR occurred. Note that in such case the |
|
* EN_CRCCOM flag in the CONFIG3 register may have been reset causing the |
|
* CRC not being computed by the ADC thus triggering the EILSEQ error. |
|
*/ |
|
LOG_WRN("Power cycle reset occurred, attempting to reset the ADC"); |
|
err = adc_mcp356xr_reset_after_power_on(dev); |
|
if (err) { |
|
LOG_ERR("Failed to properly reset ADC after a power cycle occurred " |
|
"(error = %d)", |
|
err); |
|
return err; |
|
} |
|
} else if (err) { |
|
return err; |
|
} |
|
|
|
if (por_occurred != NULL) { |
|
*por_occurred = local_por_occurred; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int adc_mcp356xr_send_configuration(const struct device *dev, uint8_t channel_id) |
|
{ |
|
const struct adc_mcp356xr_config *config = dev->config; |
|
struct adc_mcp356xr_data *data = dev->data; |
|
|
|
struct channel_registers channel_registers = |
|
adc_mcp356xr_channel_registers_get(dev, channel_id); |
|
|
|
uint8_t config_buffer[REGISTER_ARRAY_SIZE] = { |
|
[REGISTER_INDEX(CONFIG0)] = channel_registers.config0, |
|
[REGISTER_INDEX(CONFIG2)] = channel_registers.config2, |
|
[REGISTER_INDEX(MUX)] = channel_registers.mux, |
|
[REGISTER_INDEX(CONFIG1)] = data->config1_register, |
|
[REGISTER_INDEX(CONFIG3)] = config->init_reg_value[REGISTER_INDEX(CONFIG3)], |
|
[REGISTER_INDEX(IRQ)] = config->init_reg_value[REGISTER_INDEX(IRQ)], |
|
}; |
|
|
|
bool internal_vref_selected = |
|
(MCP356XR_REGISTER_FIELD_GET(CONFIG0, VREF_SEL, channel_registers.config0) == |
|
MCP356XR_REGISTER_CONFIG0_VREF_SEL_INTERNAL_VOLTAGE_REF); |
|
|
|
if (internal_vref_selected != data->internal_vref_in_use) { |
|
/* we are not using the same Vref anymore so we must wait for things to stabilize */ |
|
data->wait_for_vref_stabilization = true; |
|
} |
|
|
|
data->internal_vref_in_use = internal_vref_selected; |
|
|
|
int err = adc_mcp356xr_incremental_write(dev, MCP356XR_REGISTER_ADDRESS_CONFIG0, |
|
config_buffer, ARRAY_SIZE(config_buffer)); |
|
if (err) { |
|
LOG_ERR("Failed to write configuration (error = %d)", err); |
|
} |
|
|
|
return err; |
|
} |
|
|
|
static int adc_mcp356xr_validate_buffer_size(const struct adc_sequence *sequence) |
|
{ |
|
uint8_t channels = 0; |
|
size_t needed; |
|
|
|
channels = POPCOUNT(sequence->channels); |
|
|
|
needed = channels * sizeof(int32_t); |
|
if (sequence->options) { |
|
needed *= (1 + sequence->options->extra_samplings); |
|
} |
|
|
|
if (sequence->buffer_size < needed) { |
|
return -ENOMEM; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int adc_mcp356xr_start_read(const struct device *dev, const struct adc_sequence *sequence) |
|
{ |
|
struct adc_mcp356xr_data *data = dev->data; |
|
|
|
int err = 0; |
|
|
|
if (sequence->channels == 0) { |
|
LOG_ERR("No channel selected"); |
|
return -EINVAL; |
|
} |
|
|
|
if (find_msb_set(sequence->channels) > CONFIG_ADC_MCP356XR_ADC_CHANNEL_COUNT) { |
|
LOG_ERR("Invalid channel selection (0x%x)", sequence->channels); |
|
return -EINVAL; |
|
} |
|
|
|
if (sequence->resolution != 24) { |
|
LOG_ERR("%d bit resolution is not supported", sequence->resolution); |
|
return -ENOTSUP; |
|
} |
|
|
|
err = adc_mcp356xr_validate_buffer_size(sequence); |
|
if (err) { |
|
LOG_ERR("buffer size too small"); |
|
return err; |
|
} |
|
|
|
err = adc_mcp356xr_set_oversampling(dev, sequence->oversampling); |
|
if (err) { |
|
return err; |
|
} |
|
|
|
data->buffer = sequence->buffer; |
|
adc_context_start_read(&(data->ctx), sequence); |
|
|
|
return adc_context_wait_for_completion(&(data->ctx)); |
|
} |
|
|
|
static int adc_mcp356xr_wait_for_data(const struct device *dev) |
|
{ |
|
while (1) { |
|
int err = 0; |
|
bool data_ready = false; |
|
bool por_occurred = false; |
|
|
|
#ifdef CONFIG_ADC_MCP356XR_POLL |
|
k_sleep(K_MSEC(CONFIG_ADC_MCP356XR_POLLING_PERIOD_MS)); |
|
#else |
|
struct adc_mcp356xr_data *data = dev->data; |
|
|
|
k_sem_take(&(data->irq_occurred), K_FOREVER); |
|
#endif |
|
|
|
err = adc_mcp356xr_get_status_and_sanitize(dev, &data_ready, &por_occurred, NULL); |
|
if (err) { |
|
LOG_ERR("An error occurred while attempting to retrieve ADC status " |
|
"(error = %d)", |
|
err); |
|
return err; |
|
} |
|
|
|
if (por_occurred) { |
|
LOG_ERR("Power cycle reset occurred: stop waiting for data"); |
|
return -ENXIO; |
|
} |
|
|
|
if (data_ready) { |
|
return 0; |
|
} |
|
} |
|
} |
|
|
|
static int adc_mcp356xr_run_acquisition_sequence(const struct device *dev) |
|
{ |
|
struct adc_mcp356xr_data *data = dev->data; |
|
const struct adc_mcp356xr_config *config = dev->config; |
|
|
|
while (data->channels != 0) { |
|
int err = 0; |
|
int32_t read_value = 0; |
|
|
|
uint8_t channel_id = find_lsb_set(data->channels) - 1; |
|
|
|
err = adc_mcp356xr_send_configuration(dev, channel_id); |
|
if (err) { |
|
LOG_ERR("Failed to configure channel %d (error = %d)", channel_id, err); |
|
return err; |
|
} |
|
|
|
if (data->wait_for_vref_stabilization) { |
|
k_sleep(config->vref_settle_time); |
|
data->wait_for_vref_stabilization = false; |
|
} |
|
|
|
err = adc_mcp356xr_start_conversion(dev); |
|
if (err) { |
|
LOG_ERR("Failed to start conversion (error = %d)", err); |
|
return err; |
|
} |
|
|
|
err = adc_mcp356xr_wait_for_data(dev); |
|
if (err) { |
|
LOG_ERR("An error occurred while waiting for data (error = %d)", err); |
|
return err; |
|
} |
|
|
|
err = adc_mcp356xr_get_data(dev, &read_value); |
|
if (err) { |
|
LOG_ERR("Failed to retrieve ADC reading (error = %d)", err); |
|
return err; |
|
} |
|
|
|
*data->buffer++ = read_value; |
|
WRITE_BIT(data->channels, channel_id, 0); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void adc_context_update_buffer_pointer(struct adc_context *ctx, bool repeat_sampling) |
|
{ |
|
struct adc_mcp356xr_data *data = CONTAINER_OF(ctx, struct adc_mcp356xr_data, ctx); |
|
|
|
if (repeat_sampling) { |
|
data->buffer = data->repeat_buffer; |
|
} |
|
} |
|
|
|
static void adc_context_start_sampling(struct adc_context *ctx) |
|
{ |
|
struct adc_mcp356xr_data *data = CONTAINER_OF(ctx, struct adc_mcp356xr_data, ctx); |
|
|
|
data->channels = ctx->sequence.channels; |
|
data->repeat_buffer = data->buffer; |
|
|
|
k_sem_give(&(data->start_sequence)); |
|
} |
|
|
|
static int adc_mcp356xr_channel_setup(const struct device *dev, |
|
const struct adc_channel_cfg *channel_cfg) |
|
{ |
|
int err = 0; |
|
struct channel_registers registers; |
|
|
|
if (channel_cfg->channel_id >= CONFIG_ADC_MCP356XR_ADC_CHANNEL_COUNT) { |
|
LOG_ERR("Channel id '%d' is not supported", channel_cfg->channel_id); |
|
return -ENOTSUP; |
|
} |
|
|
|
if (channel_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) { |
|
LOG_ERR("unsupported acquisition_time '%d'", channel_cfg->acquisition_time); |
|
return -ENOTSUP; |
|
} |
|
|
|
if (!channel_cfg->differential) { |
|
LOG_ERR("Single-ended input is not directly supported. Single-ended configuration " |
|
"is achieved by setting negative input to MCP356XR_INPUT_AGND or any other " |
|
"input connected to ground."); |
|
return -ENOTSUP; |
|
} |
|
|
|
adc_mcp356xr_channel_registers_init(dev, ®isters); |
|
|
|
err = adc_mcp356xr_set_gain(®isters, channel_cfg->gain); |
|
if (err) { |
|
return err; |
|
} |
|
|
|
err = adc_mcp356xr_set_inputs(®isters, channel_cfg->input_positive, |
|
channel_cfg->input_negative); |
|
if (err) { |
|
return err; |
|
} |
|
|
|
err = adc_mcp356xr_set_reference(®isters, channel_cfg->reference); |
|
if (err) { |
|
return err; |
|
} |
|
|
|
adc_mcp356xr_channel_registers_set(dev, channel_cfg->channel_id, registers); |
|
|
|
return 0; |
|
} |
|
|
|
static int adc_mcp356xr_read(const struct device *dev, const struct adc_sequence *sequence) |
|
{ |
|
struct adc_mcp356xr_data *data = dev->data; |
|
|
|
int err = 0; |
|
|
|
adc_context_lock(&(data->ctx), false, NULL); |
|
err = adc_mcp356xr_start_read(dev, sequence); |
|
adc_context_release(&(data->ctx), err); |
|
|
|
return err; |
|
} |
|
|
|
#ifdef CONFIG_ADC_ASYNC |
|
static int adc_mcp356xr_read_async(const struct device *dev, const struct adc_sequence *sequence, |
|
struct k_poll_signal *async) |
|
{ |
|
struct adc_mcp356xr_data *data = dev->data; |
|
|
|
int err = 0; |
|
|
|
adc_context_lock(&(data->ctx), true, async); |
|
err = adc_mcp356xr_start_read(dev, sequence); |
|
adc_context_release(&(data->ctx), err); |
|
|
|
return err; |
|
} |
|
#endif |
|
|
|
static void adc_mcp356xr_acquisition_thread(void *p1, void *p2, void *p3) |
|
{ |
|
ARG_UNUSED(p2); |
|
ARG_UNUSED(p3); |
|
|
|
struct adc_mcp356xr_data *data = p1; |
|
const struct device *dev = data->dev; |
|
|
|
while (1) { |
|
int err = 0; |
|
|
|
k_sem_take(&(data->start_sequence), K_FOREVER); |
|
|
|
#ifndef CONFIG_ADC_MCP356XR_POLL |
|
k_sem_reset(&(data->irq_occurred)); |
|
#endif |
|
|
|
/* clear any leftover flags and sanitize state */ |
|
err = adc_mcp356xr_get_status_and_sanitize(dev, NULL, NULL, NULL); |
|
if (err) { |
|
LOG_ERR("Failed to sanitize ADC state (error = %d)", err); |
|
adc_context_complete(&(data->ctx), err); |
|
continue; |
|
} |
|
|
|
err = adc_mcp356xr_run_acquisition_sequence(dev); |
|
if (err) { |
|
adc_context_complete(&(data->ctx), err); |
|
continue; |
|
} |
|
|
|
adc_context_on_sampling_done(&(data->ctx), data->dev); |
|
} |
|
} |
|
|
|
static int adc_mcp356xr_init(const struct device *dev) |
|
{ |
|
const struct adc_mcp356xr_config *config = dev->config; |
|
struct adc_mcp356xr_data *data = dev->data; |
|
|
|
int err = 0; |
|
|
|
if (!spi_is_ready_dt(&(config->spi))) { |
|
LOG_ERR("SPI device not ready"); |
|
return -ENODEV; |
|
} |
|
|
|
err = adc_mcp356xr_reset_after_power_on(dev); |
|
if (err) { |
|
return err; |
|
} |
|
|
|
#ifndef CONFIG_ADC_MCP356XR_POLL |
|
err = adc_mcp356xr_irq_init(dev); |
|
if (err) { |
|
return err; |
|
} |
|
#endif |
|
|
|
k_sem_init(&(data->start_sequence), 0, 1); |
|
|
|
err = k_mutex_init(&(data->channel_registers_mutex)); |
|
if (err) { |
|
LOG_ERR("Failed to initialize internal mutex"); |
|
} |
|
|
|
adc_context_init(&(data->ctx)); |
|
|
|
k_tid_t tid = |
|
k_thread_create(&(data->thread), data->stack, K_KERNEL_STACK_SIZEOF(data->stack), |
|
adc_mcp356xr_acquisition_thread, (void *)data, NULL, NULL, |
|
CONFIG_ADC_MCP356XR_THREAD_PRIORITY, 0, K_NO_WAIT); |
|
|
|
k_thread_name_set(tid, dev->name); |
|
|
|
adc_context_unlock_unconditionally(&(data->ctx)); |
|
|
|
return 0; |
|
} |
|
|
|
static DEVICE_API(adc, adc_mcp356xr_api) = { |
|
.channel_setup = adc_mcp356xr_channel_setup, |
|
.read = adc_mcp356xr_read, |
|
#ifdef CONFIG_ADC_ASYNC |
|
.read_async = adc_mcp356xr_read_async, |
|
#endif |
|
.ref_internal = 2400, |
|
}; |
|
|
|
#define MCP356XR_SPI_CS_DELAY (1) |
|
#define MCP356XR_SPI_OPERATION \ |
|
(SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8) | SPI_LINES_SINGLE) |
|
|
|
#ifdef CONFIG_ADC_MCP356XR_USE_READ_CRC |
|
#define ADC_MCP356XR_INIT_CONFIG3_EN_CRCCOM MCP356XR_REGISTER_CONFIG_FLAG_ENABLED |
|
#else |
|
#define ADC_MCP356XR_INIT_CONFIG3_EN_CRCCOM MCP356XR_REGISTER_CONFIG_FLAG_DISABLED |
|
#endif |
|
|
|
#define DT_INST_CLK_SEL(index) \ |
|
DT_INST_PROP(index, use_internal_clock) \ |
|
? (DT_INST_PROP(index, enable_analog_clock_output) \ |
|
? MCP356XR_REGISTER_CONFIG0_CLK_SEL_INTERNAL_CLOCK_OUTPUT_AMCLK \ |
|
: MCP356XR_REGISTER_CONFIG0_CLK_SEL_INTERNAL_CLOCK_NO_OUTPUT) \ |
|
: MCP356XR_REGISTER_CONFIG0_CLK_SEL_EXTERNAL_CLOCK |
|
|
|
#define DT_INST_PRE(index) DT_INST_PROP(index, analog_clock_prescaler) |
|
|
|
#define DT_INST_BOOST(index) DT_INST_PROP(index, boost_current_bias) |
|
|
|
#define DT_INST_AZ_MUX(index) \ |
|
DT_INST_PROP(index, enable_adc_offset_cancellation) \ |
|
? MCP356XR_REGISTER_CONFIG_FLAG_ENABLED : MCP356XR_REGISTER_CONFIG_FLAG_DISABLED |
|
|
|
#define DT_INST_AZ_REF(index) \ |
|
DT_INST_PROP(index, enable_internal_vref_offset_cancellation) \ |
|
? MCP356XR_REGISTER_CONFIG_FLAG_ENABLED : MCP356XR_REGISTER_CONFIG_FLAG_DISABLED |
|
|
|
#define DT_INST_IRQ_MODE(index) \ |
|
DT_INST_PROP(index, irq_pin_drive_open_drain) \ |
|
? MCP356XR_REGISTER_IRQ_IRQ_MODE_IRQ_OUTPUT_OPEN_DRAIN \ |
|
: MCP356XR_REGISTER_IRQ_IRQ_MODE_IRQ_OUTPUT_PUSH_PULL |
|
|
|
#define ADC_MCP356XR_INIT_CONFIG0(index) \ |
|
MCP356XR_REGISTER_FIELD_PREP( \ |
|
CONFIG0, CONFIG0, \ |
|
MCP356XR_REGISTER_CONFIG0_CONFIG0_DO_NOT_ENTER_PARTIAL_SHUTDOWN) | \ |
|
MCP356XR_REGISTER_FIELD_PREP(CONFIG0, CLK_SEL, DT_INST_CLK_SEL(index)) | \ |
|
MCP356XR_REGISTER_FIELD_PREP(CONFIG0, CS_SEL, \ |
|
MCP356XR_REGISTER_CONFIG0_CS_SEL_NO_CURRENT) | \ |
|
MCP356XR_REGISTER_FIELD_PREP(CONFIG0, ADC_MODE, \ |
|
MCP356XR_REGISTER_CONFIG0_ADC_MODE_STANDBY) |
|
|
|
#define ADC_MCP356XR_INIT_CONFIG1(index) \ |
|
MCP356XR_REGISTER_FIELD_PREP(CONFIG1, PRE, DT_INST_PRE(index)) |
|
|
|
#define ADC_MCP356XR_INIT_CONFIG2(index) \ |
|
MCP356XR_REGISTER_FIELD_PREP(CONFIG2, BOOST, DT_INST_BOOST(index)) | \ |
|
MCP356XR_REGISTER_FIELD_PREP(CONFIG2, AZ_MUX, DT_INST_AZ_MUX(index)) | \ |
|
MCP356XR_REGISTER_FIELD_PREP(CONFIG2, AZ_REF, DT_INST_AZ_REF(index)) | \ |
|
0x1 /* see datasheet page 93 : bit 1 must be set to "1" */ |
|
|
|
#define ADC_MCP356XR_INIT_CONFIG3(index) \ |
|
MCP356XR_REGISTER_FIELD_PREP(CONFIG3, CONV_MODE, \ |
|
MCP356XR_REGISTER_CONFIG3_CONV_MODE_ONE_SHOT_GO_STANDBY) | \ |
|
MCP356XR_REGISTER_FIELD_PREP( \ |
|
CONFIG3, DATA_FORMAT, \ |
|
MCP356XR_REGISTER_CONFIG3_DATA_FORMAT_8_BITS_SIGN_24_BITS_DATA) | \ |
|
MCP356XR_REGISTER_FIELD_PREP(CONFIG3, CRC_FORMAT, \ |
|
MCP356XR_REGISTER_CONFIG3_CRC_FORMAT_16_BITS_CRC) | \ |
|
MCP356XR_REGISTER_FIELD_PREP(CONFIG3, EN_CRCCOM, \ |
|
ADC_MCP356XR_INIT_CONFIG3_EN_CRCCOM) | \ |
|
MCP356XR_REGISTER_FIELD_PREP(CONFIG3, EN_OFFCAL, \ |
|
MCP356XR_REGISTER_CONFIG_FLAG_DISABLED) | \ |
|
MCP356XR_REGISTER_FIELD_PREP(CONFIG3, EN_GAINCAL, \ |
|
MCP356XR_REGISTER_CONFIG_FLAG_DISABLED) |
|
|
|
#define ADC_MCP356XR_INIT_IRQ(index) \ |
|
MCP356XR_REGISTER_FIELD_PREP(IRQ, EN_FASTCMD, MCP356XR_REGISTER_CONFIG_FLAG_ENABLED) | \ |
|
MCP356XR_REGISTER_FIELD_PREP(IRQ, EN_STP, \ |
|
MCP356XR_REGISTER_CONFIG_FLAG_DISABLED) | \ |
|
MCP356XR_REGISTER_FIELD_PREP(IRQ, IRQ_MODE, DT_INST_IRQ_MODE(index)) |
|
|
|
#define ADC_MCP356XR_INIT(index) \ |
|
static const struct adc_mcp356xr_config adc_mcp356xr_config_##index = { \ |
|
.spi = SPI_DT_SPEC_INST_GET(index, MCP356XR_SPI_OPERATION, MCP356XR_SPI_CS_DELAY), \ |
|
.address = DT_INST_PROP(index, address), \ |
|
.vref_settle_time = K_MSEC(DT_INST_PROP_OR(index, vref_settle_time_ms, 0)), \ |
|
.init_reg_value = \ |
|
{ \ |
|
[REGISTER_INDEX(CONFIG0)] = ADC_MCP356XR_INIT_CONFIG0(index), \ |
|
[REGISTER_INDEX(CONFIG1)] = ADC_MCP356XR_INIT_CONFIG1(index), \ |
|
[REGISTER_INDEX(CONFIG2)] = ADC_MCP356XR_INIT_CONFIG2(index), \ |
|
[REGISTER_INDEX(CONFIG3)] = ADC_MCP356XR_INIT_CONFIG3(index), \ |
|
[REGISTER_INDEX(IRQ)] = ADC_MCP356XR_INIT_IRQ(index), \ |
|
[REGISTER_INDEX(MUX)] = 0, \ |
|
}, \ |
|
IF_DISABLED(CONFIG_ADC_MCP356XR_POLL, \ |
|
(.irq = GPIO_DT_SPEC_INST_GET(index, irq_gpios),)) \ |
|
}; \ |
|
\ |
|
static struct adc_mcp356xr_data adc_mcp356xr_data_##index = { \ |
|
ADC_CONTEXT_INIT_TIMER(adc_mcp356xr_data_##index, ctx), \ |
|
ADC_CONTEXT_INIT_LOCK(adc_mcp356xr_data_##index, ctx), \ |
|
ADC_CONTEXT_INIT_SYNC(adc_mcp356xr_data_##index, ctx), \ |
|
.dev = DEVICE_DT_INST_GET(index), \ |
|
}; \ |
|
\ |
|
DEVICE_DT_INST_DEFINE(index, &adc_mcp356xr_init, NULL, &adc_mcp356xr_data_##index, \ |
|
&adc_mcp356xr_config_##index, POST_KERNEL, CONFIG_ADC_INIT_PRIORITY, \ |
|
&adc_mcp356xr_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(ADC_MCP356XR_INIT)
|
|
|