Skip to content

Commit aa59a42

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 b6cfb45 commit aa59a42

File tree

8 files changed

+669
-2
lines changed

8 files changed

+669
-2
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 SUBALIGN ${CONFIG_LINKER_ITERABLE_SUBALIGN})
153153
endif()
154154

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

drivers/adc/CMakeLists.txt

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

0 commit comments

Comments
 (0)