| /* |
| * 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) |