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.
452 lines
12 KiB
452 lines
12 KiB
/* |
|
* 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)
|
|
|