Skip to content

Commit 3e916c0

Browse files
driver: adc: Added stream APIs for ADC
Introduce a streaming APIs for ADC devices. Two new APIs are added to the adc_driver_api: submit and get_decoder. Added decoder following APIs: get_frame_count, get_size_info, decode, has_trigger. Supported triggers are: - ADC_TRIG_DATA_READY - ADC_TRIG_FIFO_WATERMARK - ADC_TRIG_FIFO_FULL Supported operations to be done on trigger: - include - whatever data is associated with the trigger - nop - do nothing with data associated with the trigger - drop - clear data associated with the trigger Some changes to the linker scripts were needed to add decoder APIs. Signed-off-by: Vladislav Pejic <vladislav.pejic@orioninc.com>
1 parent 0903efa commit 3e916c0

File tree

7 files changed

+675
-1
lines changed

7 files changed

+675
-1
lines changed

cmake/linker_script/common/common-rom.cmake

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ if(CONFIG_SENSOR_ASYNC_API)
153153
zephyr_iterable_section(NAME sensor_decoder_api KVMA RAM_REGION GROUP RODATA_REGION)
154154
endif()
155155

156+
if(CONFIG_ADC_STREAM)
157+
zephyr_iterable_section(NAME adc_decoder_api KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN ${CONFIG_LINKER_ITERABLE_SUBALIGN})
158+
endif()
159+
156160
if(CONFIG_MCUMGR)
157161
zephyr_iterable_section(NAME mcumgr_handler KVMA RAM_REGION GROUP RODATA_REGION)
158162
endif()

drivers/adc/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ zephyr_library_sources_ifdef(CONFIG_ADC_MAX32 adc_max32.c)
6565
zephyr_library_sources_ifdef(CONFIG_ADC_AD4114 adc_ad4114.c)
6666
zephyr_library_sources_ifdef(CONFIG_ADC_AD7124 adc_ad7124.c)
6767
zephyr_library_sources_ifdef(CONFIG_ADC_AD405X adc_ad405x.c)
68+
zephyr_library_sources_ifdef(CONFIG_ADC_STREAM default_rtio_adc.c)
6869
zephyr_library_sources_ifdef(CONFIG_ADC_AD4130 adc_ad4130.c)
6970
zephyr_library_sources_ifdef(CONFIG_ADC_REALTEK_RTS5912 adc_realtek_rts5912.c)
7071
zephyr_library_sources_ifdef(CONFIG_ADC_TI_AM335X adc_ti_am335x.c)

drivers/adc/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ config ADC_INIT_PRIORITY
5050
help
5151
ADC driver device initialization priority.
5252

53+
config ADC_STREAM
54+
bool "ADC stream support"
55+
select RTIO
56+
select RTIO_SYS_MEM_BLOCKS
57+
select RTIO_CONSUME_SEM
58+
select RTIO_WORKQ
59+
help
60+
This option enables the stream API calls.
61+
5362
module = ADC
5463
module-str = ADC
5564
source "subsys/logging/Kconfig.template.log_config"

drivers/adc/default_rtio_adc.c

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
/*
2+
* Copyright (c) 2023 Google LLC.
3+
* Copyright (c) 2024 Croxel Inc.
4+
* Copyright (c) 2025 Analog Devices, Inc.
5+
*
6+
* SPDX-License-Identifier: Apache-2.0
7+
*/
8+
9+
#include <zephyr/drivers/adc.h>
10+
#include <zephyr/logging/log.h>
11+
#include <zephyr/rtio/work.h>
12+
13+
LOG_MODULE_REGISTER(adc_compat, CONFIG_ADC_LOG_LEVEL);
14+
15+
static void adc_submit_fallback(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe);
16+
17+
static void adc_iodev_submit(struct rtio_iodev_sqe *iodev_sqe)
18+
{
19+
const struct adc_read_config *cfg = iodev_sqe->sqe.iodev->data;
20+
const struct device *dev = cfg->adc;
21+
const struct adc_driver_api *api = dev->api;
22+
23+
if (api->submit != NULL) {
24+
api->submit(dev, iodev_sqe);
25+
} else if (!cfg->is_streaming) {
26+
adc_submit_fallback(dev, iodev_sqe);
27+
} else {
28+
rtio_iodev_sqe_err(iodev_sqe, -ENOTSUP);
29+
}
30+
}
31+
32+
const struct rtio_iodev_api __adc_iodev_api = {
33+
.submit = adc_iodev_submit,
34+
};
35+
36+
/**
37+
* @brief Compute the required header size
38+
*
39+
* This function takes into account alignment of the q31 values that will follow the header.
40+
*
41+
* @param[in] num_output_samples The number of samples to represent
42+
* @return The number of bytes needed for this sample frame's header
43+
*/
44+
static inline uint32_t compute_read_buf_size(const struct adc_dt_spec *adc_spec, int num_channels)
45+
{
46+
uint32_t size = 0;
47+
48+
for (int i = 0; i < num_channels; ++i) {
49+
size += adc_spec[i].resolution / 8;
50+
if (adc_spec[i].resolution % 8) {
51+
size++;
52+
}
53+
}
54+
55+
/* Align to 4 bytes */
56+
if (size % 4) {
57+
size += 4 - (size % 4);
58+
}
59+
60+
return size;
61+
}
62+
63+
/**
64+
* @brief Compute the required header size
65+
*
66+
* This function takes into account alignment of the q31 values that will follow the header.
67+
*
68+
* @param[in] num_output_samples The number of samples to represent
69+
* @return The number of bytes needed for this sample frame's header
70+
*/
71+
static inline uint32_t compute_header_size(int num_output_samples)
72+
{
73+
uint32_t size = sizeof(struct adc_data_generic_header) +
74+
(num_output_samples * sizeof(struct adc_chan_spec));
75+
return (size + 3) & ~0x3;
76+
}
77+
78+
/**
79+
* @brief Compute the minimum number of bytes needed
80+
*
81+
* @param[in] num_output_samples The number of samples to represent
82+
* @return The number of bytes needed for this sample frame
83+
*/
84+
static inline uint32_t compute_min_buf_len(int num_output_samples)
85+
{
86+
return compute_header_size(num_output_samples) + (num_output_samples * sizeof(q31_t));
87+
}
88+
89+
/**
90+
* @brief Convert sample to q31_t format
91+
*
92+
* @param[in] out Pointer to the output q31_t value
93+
* @param[in] data_in The input data to convert
94+
* @param[in] channel The ADC channel specification
95+
* @param[in] adc_shift The shift value for the ADC
96+
*/
97+
static inline void adc_convert_q31(q31_t *out, uint64_t data_in,
98+
const struct adc_dt_spec *adc_spec, uint8_t adc_shift)
99+
{
100+
uint32_t scale = BIT(adc_spec->resolution);
101+
uint8_t data_size = adc_spec->resolution / 8;
102+
103+
if (adc_spec->resolution % 8) {
104+
data_size++;
105+
}
106+
107+
/* In Differential mode, 1 bit is used for sign */
108+
if (adc_spec->channel_cfg.differential) {
109+
scale = BIT(adc_spec->resolution - 1);
110+
}
111+
112+
uint32_t sensitivity = (adc_spec->vref_mv * (scale - 1)) / scale
113+
* 1000 / scale; /* uV / LSB */
114+
115+
*out = BIT(31 - adc_shift)/* scaling to q_31*/ * sensitivity / 1000000/*uV to V*/ * data_in;
116+
}
117+
118+
/**
119+
* @brief Compute the number of bits needed to represent the vref_mv
120+
*
121+
* @param[in] vref_mv The reference voltage in mV
122+
* @return The number of bits needed to represent the vref_mv
123+
*/
124+
uint8_t adc_convert_vref_to_shift(uint16_t vref_mv)
125+
{
126+
uint8_t count = 1;
127+
128+
while (1) {
129+
vref_mv /= 2;
130+
if (vref_mv) {
131+
count++;
132+
} else {
133+
break;
134+
}
135+
}
136+
return count;
137+
}
138+
139+
/**
140+
* @brief Fallback function for retrofiting old drivers to rtio (sync)
141+
*
142+
* @param[in] iodev_sqe The read submission queue event
143+
*/
144+
static void adc_submit_fallback_sync(struct rtio_iodev_sqe *iodev_sqe)
145+
{
146+
const struct adc_read_config *cfg = iodev_sqe->sqe.iodev->data;
147+
const struct device *dev = cfg->adc;
148+
const struct adc_dt_spec *adc_spec = cfg->adc_spec;
149+
const int num_output_samples = cfg->adc_spec_cnt;
150+
uint32_t min_buf_len = compute_min_buf_len(num_output_samples);
151+
uint64_t timestamp_ns = k_ticks_to_ns_floor64(k_uptime_ticks());
152+
uint8_t read_buf_size = compute_read_buf_size(adc_spec, num_output_samples);
153+
uint8_t sample_buffer[read_buf_size];
154+
struct adc_sequence sequence = {
155+
.buffer = sample_buffer,
156+
.buffer_size = read_buf_size,
157+
};
158+
int rc = adc_read(dev, &sequence);
159+
160+
uint8_t *buf;
161+
uint32_t buf_len;
162+
163+
/* Check that the fetch succeeded */
164+
if (rc != 0) {
165+
LOG_WRN("Failed to fetch samples");
166+
rtio_iodev_sqe_err(iodev_sqe, rc);
167+
return;
168+
}
169+
170+
/* Get the buffer for the frame, it may be allocated dynamically by the rtio context */
171+
rc = rtio_sqe_rx_buf(iodev_sqe, min_buf_len, min_buf_len, &buf, &buf_len);
172+
if (rc != 0) {
173+
LOG_WRN("Failed to get a read buffer of size %u bytes", min_buf_len);
174+
rtio_iodev_sqe_err(iodev_sqe, rc);
175+
return;
176+
}
177+
178+
/* Set the timestamp and num_channels */
179+
struct adc_data_generic_header *header = (struct adc_data_generic_header *)buf;
180+
181+
header->timestamp_ns = timestamp_ns;
182+
header->num_channels = num_output_samples;
183+
header->shift = 0;
184+
185+
q31_t *q = (q31_t *)(buf + compute_header_size(num_output_samples));
186+
uint8_t *sample_pointer = sample_buffer;
187+
188+
/* Populate values, update shift, and set channels */
189+
for (size_t i = 0; i < num_output_samples; ++i) {
190+
uint8_t sample_size = adc_spec[i].resolution / 8;
191+
192+
if (adc_spec[i].resolution % 8) {
193+
sample_size++;
194+
}
195+
196+
uint64_t sample = 0;
197+
198+
memcpy(&sample, sample_pointer, sample_size);
199+
sample_pointer += sample_size;
200+
if ((adc_spec[i].channel_cfg.differential) &&
201+
(sample & (BIT(adc_spec[i].resolution - 1)))) {
202+
sample |= ~BIT_MASK(adc_spec[i].resolution);
203+
}
204+
205+
header->channels[i].chan_idx = adc_spec[i].channel_id;
206+
header->channels[i].chan_resolution = adc_spec[i].resolution;
207+
208+
int8_t new_shift = adc_convert_vref_to_shift(adc_spec[i].vref_mv);
209+
210+
if (header->shift < new_shift) {
211+
/*
212+
* Shift was updated, need to convert all the existing q values. This could
213+
* be optimized by calling zdsp_scale_q31() but that would force a
214+
* dependency between sensors and the zDSP subsystem.
215+
*/
216+
for (int q_idx = 0; q_idx < i; ++q_idx) {
217+
q[q_idx] = q[q_idx] >> (new_shift - header->shift);
218+
}
219+
header->shift = new_shift;
220+
}
221+
222+
adc_convert_q31(&q[i], sample, &adc_spec[i], header->shift);
223+
}
224+
LOG_DBG("Total channels in header: %" PRIu32, header->num_channels);
225+
rtio_iodev_sqe_ok(iodev_sqe, 0);
226+
}
227+
228+
/**
229+
* @brief Fallback function for retrofiting old drivers to rtio
230+
*
231+
* @param[in] dev The ADC device to read
232+
* @param[in] iodev_sqe The read submission queue event
233+
*/
234+
static void adc_submit_fallback(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe)
235+
{
236+
struct rtio_work_req *req = rtio_work_req_alloc();
237+
238+
if (req == NULL) {
239+
LOG_ERR("RTIO work item allocation failed. Consider to increase "
240+
"CONFIG_RTIO_WORKQ_POOL_ITEMS.");
241+
rtio_iodev_sqe_err(iodev_sqe, -ENOMEM);
242+
return;
243+
}
244+
245+
rtio_work_req_submit(req, iodev_sqe, adc_submit_fallback_sync);
246+
}
247+
248+
/**
249+
* @brief Default decoder get frame count
250+
*
251+
* Default reader can only ever service a single frame at a time.
252+
*
253+
* @param[in] buffer The data buffer to parse
254+
* @param[in] channel The channel to get the count for
255+
* @param[out] frame_count The number of frames in the buffer (always 1)
256+
* @return 0 in all cases
257+
*/
258+
static int get_frame_count(const uint8_t *buffer, uint32_t channel, uint16_t *frame_count)
259+
{
260+
*frame_count = 1;
261+
return 0;
262+
}
263+
264+
int adc_natively_supported_channel_size_info(struct adc_dt_spec adc_spec, uint32_t channel,
265+
size_t *base_size, size_t *frame_size)
266+
{
267+
__ASSERT_NO_MSG(base_size != NULL);
268+
__ASSERT_NO_MSG(frame_size != NULL);
269+
270+
*base_size = sizeof(struct adc_data);
271+
*frame_size = sizeof(struct adc_sample_data);
272+
return 0;
273+
}
274+
275+
static int get_q31_value(const struct adc_data_generic_header *header, const q31_t *values,
276+
uint32_t channel, q31_t *out)
277+
{
278+
for (size_t i = 0; i < header->num_channels; ++i) {
279+
if (channel == header->channels[i].chan_idx) {
280+
*out = values[i];
281+
return 0;
282+
}
283+
}
284+
285+
return -EINVAL;
286+
}
287+
288+
/**
289+
* @brief Decode up to N samples from the buffer
290+
*
291+
* This function will never wrap frames. If 1 channel is available in the current frame and
292+
* @p max_count is 2, only 1 channel will be decoded and the frame iterator will be modified
293+
* so that the next call to decode will begin at the next frame.
294+
*
295+
* @param[in] buffer The buffer provided on the :c:struct:`rtio` context
296+
* @param[in] channel The channel to decode
297+
* @param[in,out] fit The current frame iterator
298+
* @param[in] max_count The maximum number of channels to decode.
299+
* @param[out] data_out The decoded data
300+
* @return 0 no more samples to decode
301+
* @return >0 the number of decoded frames
302+
* @return <0 on error
303+
*/
304+
static int decode(const uint8_t *buffer, uint32_t channel, uint32_t *fit,
305+
uint16_t max_count, void *data_out)
306+
{
307+
const struct adc_data_generic_header *header =
308+
(const struct adc_data_generic_header *)buffer;
309+
const q31_t *q = (const q31_t *)(buffer + compute_header_size(header->num_channels));
310+
struct adc_data *data_out_q31 = (struct adc_data *)data_out;
311+
312+
if (*fit != 0 || max_count < 1) {
313+
return -EINVAL;
314+
}
315+
316+
data_out_q31->header.base_timestamp_ns = header->timestamp_ns;
317+
data_out_q31->header.reading_count = 1;
318+
data_out_q31->shift = header->shift;
319+
data_out_q31->readings[0].timestamp_delta = 0;
320+
321+
*fit = 1;
322+
323+
return get_q31_value(header, q, channel, &data_out_q31->readings[0].value);
324+
}
325+
326+
const struct adc_decoder_api __adc_default_decoder = {
327+
.get_frame_count = get_frame_count,
328+
.get_size_info = adc_natively_supported_channel_size_info,
329+
.decode = decode,
330+
};

0 commit comments

Comments
 (0)