blob: 208b56f726d15910f7ed26d8bbb6851e0e657bce [file] [log] [blame]
/*
* Copyright (c) 2023 Google LLC.
* Copyright (c) 2024 Croxel Inc.
* Copyright (c) 2025 Analog Devices, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/adc.h>
#include <zephyr/logging/log.h>
#include <zephyr/rtio/work.h>
LOG_MODULE_REGISTER(adc_compat, CONFIG_ADC_LOG_LEVEL);
#if CONFIG_RTIO_WORKQ
static void adc_submit_fallback(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe);
#endif /* CONFIG_RTIO_WORKQ */
static void adc_iodev_submit(struct rtio_iodev_sqe *iodev_sqe)
{
const struct adc_read_config *cfg = iodev_sqe->sqe.iodev->data;
const struct device *dev = cfg->adc;
const struct adc_driver_api *api = dev->api;
if (api->submit != NULL) {
api->submit(dev, iodev_sqe);
#if CONFIG_RTIO_WORKQ
} else if (!cfg->is_streaming) {
adc_submit_fallback(dev, iodev_sqe);
#endif /* CONFIG_RTIO_WORKQ */
} else {
rtio_iodev_sqe_err(iodev_sqe, -ENOTSUP);
}
}
const struct rtio_iodev_api __adc_iodev_api = {
.submit = adc_iodev_submit,
};
/**
* @brief Compute the required header size
*
* This function takes into account alignment of the q31 values that will follow the header.
*
* @param[in] num_output_samples The number of samples to represent
* @return The number of bytes needed for this sample frame's header
*/
static inline uint32_t compute_read_buf_size(const struct adc_dt_spec *adc_spec, int num_channels)
{
uint32_t size = 0;
for (int i = 0; i < num_channels; ++i) {
size += adc_spec[i].resolution / 8;
if (adc_spec[i].resolution % 8) {
size++;
}
}
/* Align to 4 bytes */
if (size % 4) {
size += 4 - (size % 4);
}
return size;
}
/**
* @brief Compute the required header size
*
* This function takes into account alignment of the q31 values that will follow the header.
*
* @param[in] num_output_samples The number of samples to represent
* @return The number of bytes needed for this sample frame's header
*/
static inline uint32_t compute_header_size(int num_output_samples)
{
uint32_t size = sizeof(struct adc_data_generic_header) +
(num_output_samples * sizeof(struct adc_chan_spec));
return (size + 3) & ~0x3;
}
/**
* @brief Compute the minimum number of bytes needed
*
* @param[in] num_output_samples The number of samples to represent
* @return The number of bytes needed for this sample frame
*/
static inline uint32_t compute_min_buf_len(int num_output_samples)
{
return compute_header_size(num_output_samples) + (num_output_samples * sizeof(q31_t));
}
/**
* @brief Convert sample to q31_t format
*
* @param[in] out Pointer to the output q31_t value
* @param[in] data_in The input data to convert
* @param[in] channel The ADC channel specification
* @param[in] adc_shift The shift value for the ADC
*/
static inline void adc_convert_q31(q31_t *out, uint64_t data_in,
const struct adc_dt_spec *adc_spec, uint8_t adc_shift)
{
uint32_t scale = BIT(adc_spec->resolution);
uint8_t data_size = adc_spec->resolution / 8;
if (adc_spec->resolution % 8) {
data_size++;
}
/* In Differential mode, 1 bit is used for sign */
if (adc_spec->channel_cfg.differential) {
scale = BIT(adc_spec->resolution - 1);
}
uint32_t sensitivity = (adc_spec->vref_mv * (scale - 1)) / scale
* 1000 / scale; /* uV / LSB */
*out = BIT(31 - adc_shift)/* scaling to q_31*/ * sensitivity / 1000000/*uV to V*/ * data_in;
}
/**
* @brief Compute the number of bits needed to represent the vref_mv
*
* @param[in] vref_mv The reference voltage in mV
* @return The number of bits needed to represent the vref_mv
*/
uint8_t adc_convert_vref_to_shift(uint16_t vref_mv)
{
uint8_t count = 1;
while (1) {
vref_mv /= 2;
if (vref_mv) {
count++;
} else {
break;
}
}
return count;
}
#if CONFIG_RTIO_WORKQ
/**
* @brief Fallback function for retrofiting old drivers to rtio (sync)
*
* @param[in] iodev_sqe The read submission queue event
*/
static void adc_submit_fallback_sync(struct rtio_iodev_sqe *iodev_sqe)
{
const struct adc_read_config *cfg = iodev_sqe->sqe.iodev->data;
const struct device *dev = cfg->adc;
const struct adc_dt_spec *adc_spec = cfg->adc_spec;
const int num_output_samples = cfg->adc_spec_cnt;
uint32_t min_buf_len = compute_min_buf_len(num_output_samples);
uint64_t timestamp_ns = k_ticks_to_ns_floor64(k_uptime_ticks());
uint8_t read_buf_size = compute_read_buf_size(adc_spec, num_output_samples);
uint8_t sample_buffer[read_buf_size];
struct adc_sequence sequence = {
.buffer = sample_buffer,
.buffer_size = read_buf_size,
};
int rc = adc_read(dev, &sequence);
uint8_t *buf;
uint32_t buf_len;
/* Check that the fetch succeeded */
if (rc != 0) {
LOG_WRN("Failed to fetch samples");
rtio_iodev_sqe_err(iodev_sqe, rc);
return;
}
/* Get the buffer for the frame, it may be allocated dynamically by the rtio context */
rc = rtio_sqe_rx_buf(iodev_sqe, min_buf_len, min_buf_len, &buf, &buf_len);
if (rc != 0) {
LOG_WRN("Failed to get a read buffer of size %u bytes", min_buf_len);
rtio_iodev_sqe_err(iodev_sqe, rc);
return;
}
/* Set the timestamp and num_channels */
struct adc_data_generic_header *header = (struct adc_data_generic_header *)buf;
header->timestamp_ns = timestamp_ns;
header->num_channels = num_output_samples;
header->shift = 0;
q31_t *q = (q31_t *)(buf + compute_header_size(num_output_samples));
uint8_t *sample_pointer = sample_buffer;
/* Populate values, update shift, and set channels */
for (size_t i = 0; i < num_output_samples; ++i) {
uint8_t sample_size = adc_spec[i].resolution / 8;
if (adc_spec[i].resolution % 8) {
sample_size++;
}
uint64_t sample = 0;
memcpy(&sample, sample_pointer, sample_size);
sample_pointer += sample_size;
if ((adc_spec[i].channel_cfg.differential) &&
(sample & (BIT(adc_spec[i].resolution - 1)))) {
sample |= ~BIT_MASK(adc_spec[i].resolution);
}
header->channels[i].chan_idx = adc_spec[i].channel_id;
header->channels[i].chan_resolution = adc_spec[i].resolution;
int8_t new_shift = adc_convert_vref_to_shift(adc_spec[i].vref_mv);
if (header->shift < new_shift) {
/*
* Shift was updated, need to convert all the existing q values. This could
* be optimized by calling zdsp_scale_q31() but that would force a
* dependency between sensors and the zDSP subsystem.
*/
for (int q_idx = 0; q_idx < i; ++q_idx) {
q[q_idx] = q[q_idx] >> (new_shift - header->shift);
}
header->shift = new_shift;
}
adc_convert_q31(&q[i], sample, &adc_spec[i], header->shift);
}
LOG_DBG("Total channels in header: %" PRIu32, header->num_channels);
rtio_iodev_sqe_ok(iodev_sqe, 0);
}
/**
* @brief Fallback function for retrofiting old drivers to rtio
*
* @param[in] dev The ADC device to read
* @param[in] iodev_sqe The read submission queue event
*/
static void adc_submit_fallback(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe)
{
struct rtio_work_req *req = rtio_work_req_alloc();
if (req == NULL) {
LOG_ERR("RTIO work item allocation failed. Consider to increase "
"CONFIG_RTIO_WORKQ_POOL_ITEMS.");
rtio_iodev_sqe_err(iodev_sqe, -ENOMEM);
return;
}
rtio_work_req_submit(req, iodev_sqe, adc_submit_fallback_sync);
}
#endif /* CONFIG_RTIO_WORKQ */
/**
* @brief Default decoder get frame count
*
* Default reader can only ever service a single frame at a time.
*
* @param[in] buffer The data buffer to parse
* @param[in] channel The channel to get the count for
* @param[out] frame_count The number of frames in the buffer (always 1)
* @return 0 in all cases
*/
static int get_frame_count(const uint8_t *buffer, uint32_t channel, uint16_t *frame_count)
{
*frame_count = 1;
return 0;
}
int adc_natively_supported_channel_size_info(struct adc_dt_spec adc_spec, uint32_t channel,
size_t *base_size, size_t *frame_size)
{
__ASSERT_NO_MSG(base_size != NULL);
__ASSERT_NO_MSG(frame_size != NULL);
*base_size = sizeof(struct adc_data);
*frame_size = sizeof(struct adc_sample_data);
return 0;
}
static int get_q31_value(const struct adc_data_generic_header *header, const q31_t *values,
uint32_t channel, q31_t *out)
{
for (size_t i = 0; i < header->num_channels; ++i) {
if (channel == header->channels[i].chan_idx) {
*out = values[i];
return 0;
}
}
return -EINVAL;
}
/**
* @brief Decode up to N samples from the buffer
*
* This function will never wrap frames. If 1 channel is available in the current frame and
* @p max_count is 2, only 1 channel will be decoded and the frame iterator will be modified
* so that the next call to decode will begin at the next frame.
*
* @param[in] buffer The buffer provided on the :c:struct:`rtio` context
* @param[in] channel The channel to decode
* @param[in,out] fit The current frame iterator
* @param[in] max_count The maximum number of channels to decode.
* @param[out] data_out The decoded data
* @return 0 no more samples to decode
* @return >0 the number of decoded frames
* @return <0 on error
*/
static int decode(const uint8_t *buffer, uint32_t channel, uint32_t *fit,
uint16_t max_count, void *data_out)
{
const struct adc_data_generic_header *header =
(const struct adc_data_generic_header *)buffer;
const q31_t *q = (const q31_t *)(buffer + compute_header_size(header->num_channels));
struct adc_data *data_out_q31 = (struct adc_data *)data_out;
if (*fit != 0 || max_count < 1) {
return -EINVAL;
}
data_out_q31->header.base_timestamp_ns = header->timestamp_ns;
data_out_q31->header.reading_count = 1;
data_out_q31->shift = header->shift;
data_out_q31->readings[0].timestamp_delta = 0;
*fit = 1;
return get_q31_value(header, q, channel, &data_out_q31->readings[0].value);
}
const struct adc_decoder_api __adc_default_decoder = {
.get_frame_count = get_frame_count,
.get_size_info = adc_natively_supported_channel_size_info,
.decode = decode,
};