Skip to content

Commit 1d02d57

Browse files
vladislav-pejiccfriedt
authored andcommitted
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 <[email protected]>
1 parent a2b1c59 commit 1d02d57

File tree

7 files changed

+689
-1
lines changed

7 files changed

+689
-1
lines changed

cmake/linker_script/common/common-rom.cmake

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

155+
if(CONFIG_ADC_STREAM)
156+
zephyr_iterable_section(NAME adc_decoder_api KVMA RAM_REGION GROUP RODATA_REGION)
157+
endif()
158+
155159
if(CONFIG_MCUMGR)
156160
zephyr_iterable_section(NAME mcumgr_handler KVMA RAM_REGION GROUP RODATA_REGION)
157161
endif()

drivers/adc/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ zephyr_library_sources_ifdef(CONFIG_ADC_MAX32 adc_max32.c)
6868
zephyr_library_sources_ifdef(CONFIG_ADC_AD4114 adc_ad4114.c)
6969
zephyr_library_sources_ifdef(CONFIG_ADC_AD7124 adc_ad7124.c)
7070
zephyr_library_sources_ifdef(CONFIG_ADC_AD405X adc_ad405x.c)
71+
zephyr_library_sources_ifdef(CONFIG_ADC_STREAM default_rtio_adc.c)
7172
zephyr_library_sources_ifdef(CONFIG_ADC_AD4130 adc_ad4130.c)
7273
zephyr_library_sources_ifdef(CONFIG_ADC_REALTEK_RTS5912 adc_realtek_rts5912.c)
7374
zephyr_library_sources_ifdef(CONFIG_ADC_TI_AM335X adc_ti_am335x.c)

drivers/adc/Kconfig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,24 @@ 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+
help
59+
This option enables the stream API calls.
60+
61+
config ADC_DEFAULT_RTIO
62+
bool "Default RTIO support"
63+
select RTIO
64+
select RTIO_WORKQ
65+
select RTIO_SYS_MEM_BLOCKS
66+
select RTIO_CONSUME_SEM
67+
help
68+
This option enables the default RTIO handler for ADC devices that do not
69+
implement stream functionality.
70+
5371
module = ADC
5472
module-str = ADC
5573
source "subsys/logging/Kconfig.template.log_config"

drivers/adc/default_rtio_adc.c

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

0 commit comments

Comments
 (0)