The AD4114 is a low power, low noise, 24-bit, sigma-delta ADC. This driver allows to use it with the Zephyr ADC API. It uses the continuous acquisition ADC feature. This ADC allows many configutations, but this driver uses it as the most generic way : - each can channel can be enable or disable using the device tree configuration - configure two setups (one for unipolar inputs, one for bipolar inputs) - use an external clock Signed-off-by: Pierrick Curt <pierrickcurt@gmail.com>
452 lines
12 KiB
C
452 lines
12 KiB
C
/*
|
||
* Copyright (c) 2024 Pierrick Curt
|
||
*
|
||
* SPDX-License-Identifier: Apache-2.0
|
||
*/
|
||
|
||
#include <stdbool.h>
|
||
#include <zephyr/device.h>
|
||
#include <zephyr/devicetree.h>
|
||
#include <zephyr/drivers/adc.h>
|
||
#include <zephyr/drivers/spi.h>
|
||
#include <zephyr/kernel.h>
|
||
#include <zephyr/logging/log.h>
|
||
#include <zephyr/sys/byteorder.h>
|
||
#include <zephyr/sys/util.h>
|
||
|
||
#define ADC_CONTEXT_USES_KERNEL_TIMER
|
||
#include "adc_context.h"
|
||
|
||
LOG_MODULE_REGISTER(ADC_AD4114, CONFIG_ADC_LOG_LEVEL);
|
||
|
||
#define DT_DRV_COMPAT adi_ad4114_adc
|
||
|
||
#define AD4114_CMD_READ 0x40
|
||
#define AD4114_CMD_WRITE 0x0
|
||
#define AD4114_CHAN_NUMBER 16
|
||
#define AD4114_ADC_RESOLUTION 24U
|
||
|
||
enum ad4114_reg {
|
||
AD4114_STATUS_REG = 0x00,
|
||
AD4114_MODE_REG = 0x01,
|
||
AD4114_IFMODE_REG = 0x02,
|
||
AD4114_REGCHECK = 0x03,
|
||
AD4114_DATA_REG = 0x04,
|
||
AD4114_GPIOCON_REG = 0x06,
|
||
AD4114_ID_REG = 0x07,
|
||
AD4114_CHANNEL_0_REG = 0x10,
|
||
AD4114_CHANNEL_1_REG = 0x11,
|
||
AD4114_CHANNEL_2_REG = 0x12,
|
||
AD4114_CHANNEL_3_REG = 0x13,
|
||
AD4114_CHANNEL_4_REG = 0x14,
|
||
AD4114_CHANNEL_5_REG = 0x15,
|
||
AD4114_CHANNEL_6_REG = 0x16,
|
||
AD4114_CHANNEL_7_REG = 0x17,
|
||
AD4114_CHANNEL_8_REG = 0x18,
|
||
AD4114_CHANNEL_9_REG = 0x19,
|
||
AD4114_CHANNEL_10_REG = 0x1A,
|
||
AD4114_CHANNEL_11_REG = 0x1B,
|
||
AD4114_CHANNEL_12_REG = 0x1C,
|
||
AD4114_CHANNEL_13_REG = 0x1D,
|
||
AD4114_CHANNEL_14_REG = 0x1E,
|
||
AD4114_CHANNEL_15_REG = 0x1F,
|
||
AD4114_SETUPCON0_REG = 0x20,
|
||
AD4114_SETUPCON1_REG = 0x21,
|
||
AD4114_SETUPCON2_REG = 0x22,
|
||
AD4114_SETUPCON3_REG = 0x23,
|
||
AD4114_SETUPCON4_REG = 0x24,
|
||
AD4114_SETUPCON5_REG = 0x25,
|
||
AD4114_SETUPCON6_REG = 0x26,
|
||
AD4114_SETUPCON7_REG = 0x27,
|
||
AD4114_FILTCON0_REG = 0x28,
|
||
AD4114_FILTCON1_REG = 0x29,
|
||
AD4114_FILTCON2_REG = 0x2A,
|
||
AD4114_FILTCON3_REG = 0x2B,
|
||
AD4114_FILTCON4_REG = 0x2C,
|
||
AD4114_FILTCON5_REG = 0x2D,
|
||
AD4114_FILTCON6_REG = 0x2E,
|
||
AD4114_FILTCON7_REG = 0x2F,
|
||
AD4114_OFFSET0_REG = 0x30,
|
||
AD4114_OFFSET1_REG = 0x31,
|
||
AD4114_OFFSET2_REG = 0x32,
|
||
AD4114_OFFSET3_REG = 0x33,
|
||
AD4114_OFFSET4_REG = 0x34,
|
||
AD4114_OFFSET5_REG = 0x35,
|
||
AD4114_OFFSET6_REG = 0x36,
|
||
AD4114_OFFSET7_REG = 0x37,
|
||
AD4114_GAIN0_REG = 0x38,
|
||
AD4114_GAIN1_REG = 0x39,
|
||
AD4114_GAIN2_REG = 0x3A,
|
||
AD4114_GAIN3_REG = 0x3B,
|
||
AD4114_GAIN4_REG = 0x3C,
|
||
AD4114_GAIN5_REG = 0x3D,
|
||
AD4114_GAIN6_REG = 0x3E,
|
||
AD4114_GAIN7_REG = 0x3F,
|
||
};
|
||
|
||
struct adc_ad4114_config {
|
||
struct spi_dt_spec spi;
|
||
uint16_t resolution;
|
||
uint16_t map_input[AD4114_CHAN_NUMBER];
|
||
};
|
||
|
||
struct adc_ad4114_data {
|
||
struct adc_context ctx;
|
||
const struct device *dev;
|
||
struct k_thread thread;
|
||
struct k_sem sem;
|
||
uint16_t channels;
|
||
uint16_t channels_cfg;
|
||
uint32_t *buffer;
|
||
uint32_t *repeat_buffer;
|
||
|
||
K_KERNEL_STACK_MEMBER(stack, CONFIG_ADC_AD4114_ACQUISITION_THREAD_STACK_SIZE);
|
||
};
|
||
|
||
static int ad4114_write_reg(const struct device *dev, enum ad4114_reg reg_addr, uint8_t *buffer,
|
||
size_t reg_size)
|
||
{
|
||
int ret;
|
||
const struct adc_ad4114_config *config = dev->config;
|
||
uint8_t buffer_tx[5] = {0}; /* One byte command, max 4 bytes data */
|
||
|
||
const struct spi_buf tx_buf[] = {{
|
||
.buf = buffer_tx,
|
||
.len = ARRAY_SIZE(buffer_tx),
|
||
}};
|
||
const struct spi_buf_set tx = {
|
||
.buffers = tx_buf,
|
||
.count = ARRAY_SIZE(tx_buf),
|
||
};
|
||
|
||
buffer_tx[0] = AD4114_CMD_WRITE | reg_addr;
|
||
|
||
if (reg_size > 4) {
|
||
LOG_ERR("Invalid size, max data write size is 4");
|
||
return -ENOMEM;
|
||
}
|
||
/* Fill the data */
|
||
for (uint8_t index = 0; index < reg_size; index++) {
|
||
buffer_tx[1 + index] = buffer[index];
|
||
}
|
||
|
||
ret = spi_write_dt(&config->spi, &tx);
|
||
if (ret != 0) {
|
||
LOG_ERR("%s: error writing register 0x%X (%d)", dev->name, reg_addr, ret);
|
||
return ret;
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int ad4114_read_reg(const struct device *dev, enum ad4114_reg reg_addr, uint8_t *buffer,
|
||
size_t reg_size)
|
||
{
|
||
int ret;
|
||
const struct adc_ad4114_config *config = dev->config;
|
||
|
||
uint8_t buffer_tx[6] = {0};
|
||
uint8_t buffer_rx[ARRAY_SIZE(buffer_tx)] = {0xFF};
|
||
const struct spi_buf tx_buf[] = {{
|
||
.buf = buffer_tx,
|
||
.len = ARRAY_SIZE(buffer_tx),
|
||
}};
|
||
const struct spi_buf rx_buf[] = {{
|
||
.buf = buffer_rx,
|
||
.len = ARRAY_SIZE(buffer_rx),
|
||
}};
|
||
const struct spi_buf_set tx = {
|
||
.buffers = tx_buf,
|
||
.count = ARRAY_SIZE(tx_buf),
|
||
};
|
||
const struct spi_buf_set rx = {
|
||
.buffers = rx_buf,
|
||
.count = ARRAY_SIZE(rx_buf),
|
||
};
|
||
buffer_tx[0] = AD4114_CMD_READ | reg_addr;
|
||
|
||
ret = spi_transceive_dt(&config->spi, &tx, &rx);
|
||
if (ret != 0) {
|
||
LOG_ERR("%s: error reading register 0x%X (%d)", dev->name, reg_addr, ret);
|
||
return ret;
|
||
}
|
||
|
||
/* Copy received data in output buffer */
|
||
for (uint8_t index = 0; index < reg_size; index++) {
|
||
buffer[index] = buffer_rx[index + 1];
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
static void adc_context_start_sampling(struct adc_context *ctx)
|
||
{
|
||
struct adc_ad4114_data *data = CONTAINER_OF(ctx, struct adc_ad4114_data, ctx);
|
||
|
||
data->channels = ctx->sequence.channels;
|
||
data->repeat_buffer = data->buffer;
|
||
|
||
k_sem_give(&data->sem);
|
||
}
|
||
|
||
static void adc_context_update_buffer_pointer(struct adc_context *ctx, bool repeat_sampling)
|
||
{
|
||
struct adc_ad4114_data *data = CONTAINER_OF(ctx, struct adc_ad4114_data, ctx);
|
||
|
||
if (repeat_sampling) {
|
||
data->buffer = data->repeat_buffer;
|
||
}
|
||
}
|
||
|
||
static int adc_ad4114x_validate_buffer_size(const struct device *dev,
|
||
const struct adc_sequence *sequence)
|
||
{
|
||
uint8_t channels;
|
||
size_t needed;
|
||
|
||
channels = POPCOUNT(sequence->channels);
|
||
needed = channels * sizeof(uint32_t);
|
||
|
||
if (sequence->buffer_size < needed) {
|
||
return -ENOMEM;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int adc_ad4114_start_read(const struct device *dev, const struct adc_sequence *sequence)
|
||
{
|
||
struct adc_ad4114_data *data = dev->data;
|
||
const struct adc_ad4114_config *config = dev->config;
|
||
int ret;
|
||
uint8_t write_reg[2];
|
||
uint8_t status;
|
||
|
||
ret = adc_ad4114x_validate_buffer_size(dev, sequence);
|
||
if (ret < 0) {
|
||
LOG_ERR("insufficient buffer size");
|
||
return ret;
|
||
}
|
||
|
||
data->channels_cfg = sequence->channels;
|
||
for (uint32_t i = 0U; i < AD4114_CHAN_NUMBER; i++) {
|
||
if ((BIT(i) & sequence->channels) != 0) {
|
||
write_reg[0] = 0x80 | (uint8_t)((config->map_input[i] >> 8) & 0xFF);
|
||
write_reg[1] = (uint8_t)(config->map_input[i] & 0xFF);
|
||
LOG_DBG("Enable channel %d with mapping %X %X, raw %X", i, write_reg[0],
|
||
write_reg[1], config->map_input[i]);
|
||
ad4114_write_reg(dev, AD4114_CHANNEL_0_REG + i, write_reg, 2);
|
||
} else {
|
||
LOG_DBG("Disable channel %d", i);
|
||
write_reg[0] = 0x0;
|
||
write_reg[1] = 0x0;
|
||
ad4114_write_reg(dev, AD4114_CHANNEL_0_REG + i, write_reg, 2);
|
||
}
|
||
}
|
||
|
||
/* Configure the buffer */
|
||
data->buffer = sequence->buffer;
|
||
|
||
while ((status & 0x80) != 0x80) {
|
||
/* Wait for acquiistion start */
|
||
ad4114_read_reg(dev, AD4114_STATUS_REG, &status, 1);
|
||
/* Wait 10us between two status read */
|
||
k_usleep(10);
|
||
}
|
||
|
||
adc_context_start_read(&data->ctx, sequence);
|
||
|
||
return adc_context_wait_for_completion(&data->ctx);
|
||
}
|
||
|
||
static void adc_ad4114_acquisition_thread(struct adc_ad4114_data *data)
|
||
{
|
||
uint8_t value[4] = {0};
|
||
uint32_t buffer_values[AD4114_CHAN_NUMBER];
|
||
bool is_ended = false;
|
||
|
||
while (true) {
|
||
k_sem_take(&data->sem, K_FOREVER);
|
||
|
||
while (data->channels != 0) {
|
||
ad4114_read_reg(data->dev, AD4114_DATA_REG, value, 4);
|
||
/* Check the read channel */
|
||
if ((value[3] & 0xF0) != 0) {
|
||
LOG_DBG("Error read on : %X ", value[3]);
|
||
} else {
|
||
LOG_DBG("Success read on %d: value %X ", value[3],
|
||
(value[2] << 16 | value[1] << 8 | value[0]));
|
||
/* success read, store it */
|
||
buffer_values[value[3]] =
|
||
(value[0] << 16 | value[1] << 8 | value[2]);
|
||
WRITE_BIT(data->channels, value[3], 0);
|
||
/* Disable the channel after read success */
|
||
uint8_t write_reg[2] = {0};
|
||
|
||
ad4114_write_reg(data->dev, AD4114_CHANNEL_0_REG + value[3],
|
||
write_reg, 2);
|
||
}
|
||
if (data->channels == 0) {
|
||
is_ended = true;
|
||
}
|
||
/* Wait before next status ready check: the minimal acquisition time for a
|
||
* channel is 100us. So wait 10us betwen each check to avoid to use CPU for
|
||
* nothing.
|
||
*/
|
||
k_usleep(10);
|
||
}
|
||
|
||
if (is_ended) {
|
||
is_ended = false;
|
||
for (uint8_t i = 0U; i < AD4114_CHAN_NUMBER; i++) {
|
||
if ((BIT(i) & data->channels_cfg) != 0) {
|
||
*data->buffer++ = buffer_values[i];
|
||
LOG_DBG("Read channel %d value : %X ", i,
|
||
buffer_values[i]);
|
||
}
|
||
}
|
||
adc_context_on_sampling_done(&data->ctx, data->dev);
|
||
}
|
||
/* Wait 1ms before checking if a new sequence acquisition is asked */
|
||
k_usleep(1000);
|
||
}
|
||
}
|
||
|
||
static int adc_ad4114_channel_setup(const struct device *dev,
|
||
const struct adc_channel_cfg *channel_cfg)
|
||
{
|
||
|
||
/* Todo in the futur we can manage here :
|
||
* filters
|
||
* gain
|
||
* offsets
|
||
* special configuration : we can update map_input here to override the device
|
||
* tree setup
|
||
*/
|
||
if (channel_cfg->channel_id >= AD4114_CHAN_NUMBER) {
|
||
LOG_ERR("invalid channel id %d", channel_cfg->channel_id);
|
||
return -EINVAL;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int adc_ad4114_read_async(const struct device *dev, const struct adc_sequence *sequence,
|
||
struct k_poll_signal *async)
|
||
{
|
||
struct adc_ad4114_data *data = dev->data;
|
||
int ret;
|
||
|
||
adc_context_lock(&data->ctx, async ? true : false, async);
|
||
ret = adc_ad4114_start_read(dev, sequence);
|
||
adc_context_release(&data->ctx, ret);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int adc_ad4114_read(const struct device *dev, const struct adc_sequence *sequence)
|
||
{
|
||
return adc_ad4114_read_async(dev, sequence, NULL);
|
||
}
|
||
|
||
static int adc_ad4114_init(const struct device *dev)
|
||
{
|
||
int err;
|
||
const struct adc_ad4114_config *config = dev->config;
|
||
struct adc_ad4114_data *data = dev->data;
|
||
uint8_t id[2] = {0};
|
||
uint8_t gain[3];
|
||
uint8_t write_reg[2];
|
||
uint8_t status = 0;
|
||
k_tid_t tid;
|
||
|
||
data->dev = dev;
|
||
k_sem_init(&data->sem, 0, 1);
|
||
adc_context_init(&data->ctx);
|
||
|
||
if (!spi_is_ready_dt(&config->spi)) {
|
||
LOG_ERR("spi bus %s not ready", config->spi.bus->name);
|
||
return -ENODEV;
|
||
}
|
||
|
||
ad4114_read_reg(dev, AD4114_ID_REG, id, 2);
|
||
/* Check that this is the expected ID : 0x30DX, where x is don’t care */
|
||
if ((((id[0] << 8) | id[1]) & 0xFFF0) != 0x30D0) {
|
||
LOG_ERR("Read wrong ID register 0x%X 0x%X", id[0], id[1]);
|
||
return -EIO;
|
||
}
|
||
|
||
ad4114_read_reg(dev, AD4114_STATUS_REG, &status, 1);
|
||
LOG_INF("Found AD4114 with status %d", status);
|
||
|
||
/* Configure gain to 0x400000 */
|
||
gain[0] = 0x40;
|
||
gain[1] = 0x00;
|
||
gain[2] = 0x00;
|
||
ad4114_write_reg(dev, AD4114_GAIN0_REG, gain, 3);
|
||
ad4114_write_reg(dev, AD4114_GAIN1_REG, gain, 3);
|
||
|
||
/* Bit 6: DATA_STAT = 1 */
|
||
write_reg[0] = 0x0;
|
||
write_reg[1] = 0x40;
|
||
ad4114_write_reg(dev, AD4114_IFMODE_REG, write_reg, 2);
|
||
|
||
/* Bit 12: BI_UNIPOLARx = 0
|
||
* Bit 9:8 INBUFx = 11
|
||
*/
|
||
write_reg[0] = 0x3;
|
||
write_reg[1] = 0x0;
|
||
ad4114_write_reg(dev, AD4114_SETUPCON0_REG, write_reg, 2);
|
||
|
||
/* Bit 12: BI_UNIPOLARx = 1
|
||
* Bit 9:8 INBUFx = 11
|
||
*/
|
||
write_reg[0] = 0x13;
|
||
write_reg[1] = 0x0;
|
||
ad4114_write_reg(dev, AD4114_SETUPCON1_REG, write_reg, 2);
|
||
|
||
/* Bit 15: REF_EN = 1
|
||
* Bit 3:2: CLOCKSEL = 11
|
||
*/
|
||
write_reg[0] = 0x80;
|
||
write_reg[1] = 0xC;
|
||
ad4114_write_reg(dev, AD4114_MODE_REG, write_reg, 2);
|
||
|
||
tid = k_thread_create(&data->thread, data->stack, K_KERNEL_STACK_SIZEOF(data->stack),
|
||
(k_thread_entry_t)adc_ad4114_acquisition_thread, data, NULL, NULL,
|
||
CONFIG_ADC_AD4114_ACQUISITION_THREAD_PRIO, 0, K_NO_WAIT);
|
||
|
||
if (IS_ENABLED(CONFIG_THREAD_NAME)) {
|
||
err = k_thread_name_set(tid, "adc_ad4114");
|
||
if (err < 0) {
|
||
return err;
|
||
}
|
||
}
|
||
|
||
adc_context_unlock_unconditionally(&data->ctx);
|
||
return 0;
|
||
}
|
||
|
||
static DEVICE_API(adc, adc_ad4114_api) = {
|
||
.channel_setup = adc_ad4114_channel_setup,
|
||
.read = adc_ad4114_read,
|
||
};
|
||
|
||
#define FILL_MAP_INPUTS(node_id, prop, idx) \
|
||
{ \
|
||
.key_index = DT_NODE_CHILD_IDX(node_id), \
|
||
.press_mv = DT_PROP_BY_IDX(node_id, prop, idx), \
|
||
}
|
||
|
||
#define ADC_AD4114_DEVICE(inst) \
|
||
static struct adc_ad4114_data adc_ad4114_data_##inst; \
|
||
static const struct adc_ad4114_config adc_ad4114_config_##inst = { \
|
||
.spi = SPI_DT_SPEC_INST_GET(inst, SPI_WORD_SET(8), 0), \
|
||
.resolution = AD4114_ADC_RESOLUTION, \
|
||
.map_input = DT_INST_PROP(inst, map_inputs), \
|
||
}; \
|
||
DEVICE_DT_INST_DEFINE(inst, adc_ad4114_init, NULL, &adc_ad4114_data_##inst, \
|
||
&adc_ad4114_config_##inst, POST_KERNEL, CONFIG_ADC_INIT_PRIORITY, \
|
||
&adc_ad4114_api) \
|
||
BUILD_ASSERT(DT_INST_PROP_LEN(inst, map_inputs) == AD4114_CHAN_NUMBER);
|
||
|
||
DT_INST_FOREACH_STATUS_OKAY(ADC_AD4114_DEVICE)
|