diff --git a/boards/adi/apard32690/apard32690_max32690_m4.dts b/boards/adi/apard32690/apard32690_max32690_m4.dts index f291e50a80689..b78c0d0667976 100644 --- a/boards/adi/apard32690/apard32690_max32690_m4.dts +++ b/boards/adi/apard32690/apard32690_max32690_m4.dts @@ -58,8 +58,8 @@ arduino_header: connector { compatible = "arduino-header-r3"; #gpio-cells = <2>; - gpio-map-mask = <0xffffffff 0xffffffc0>; - gpio-map-pass-thru = <0 0x3f>; + gpio-map-mask = <0xffffffff 0xfffffe00>; + gpio-map-pass-thru = <0 0x1ff>; gpio-map = <0 0 &gpio3 0 0>, /* A0 */ <1 0 &gpio3 1 0>, /* A1 */ <2 0 &gpio3 2 0>, /* A2 */ diff --git a/boards/shields/eval_ad4052_ardz/boards/apard32690_max32690_m4.overlay b/boards/shields/eval_ad4052_ardz/boards/apard32690_max32690_m4.overlay index eadfaa8d4695c..90cb8cc7e08b3 100644 --- a/boards/shields/eval_ad4052_ardz/boards/apard32690_max32690_m4.overlay +++ b/boards/shields/eval_ad4052_ardz/boards/apard32690_max32690_m4.overlay @@ -9,3 +9,23 @@ io-channels = <&adc4052_eval_ad4052_ardz 0>; }; }; + +&arduino_spi { + adc4052_eval_ad4052_ardz: adc4052@0 { + conversion-gpios = <&arduino_header 12 (GPIO_ACTIVE_HIGH | MAX32_GPIO_VSEL_VDDIOH)>; + }; +}; + +&timer0 { + status = "okay"; + + counter0: counter { + status = "okay"; + }; +}; + +/ { + chosen { + zephyr,adc-clock = &counter0; + }; +}; diff --git a/boards/shields/eval_ad4052_ardz/eval_ad4052_ardz.overlay b/boards/shields/eval_ad4052_ardz/eval_ad4052_ardz.overlay index 856bdf9877c9e..6573b80ffd610 100644 --- a/boards/shields/eval_ad4052_ardz/eval_ad4052_ardz.overlay +++ b/boards/shields/eval_ad4052_ardz/eval_ad4052_ardz.overlay @@ -4,6 +4,12 @@ * SPDX-License-Identifier: Apache-2.0 */ +/ { + aliases { + adc0 = &adc4052_eval_ad4052_ardz; + }; +}; + &arduino_spi { status = "okay"; @@ -12,12 +18,12 @@ spi-max-frequency = ; gp1-gpios = <&arduino_header 14 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; gp0-gpios = <&arduino_header 15 (GPIO_PULL_DOWN)>; - conversion-gpios = <&arduino_header 12 (GPIO_ACTIVE_HIGH | MAX32_GPIO_VSEL_VDDIOH)>; + conversion-gpios = <&arduino_header 12 (GPIO_ACTIVE_HIGH)>; #address-cells = <1>; #size-cells = <0>; #io-channel-cells = <1>; status = "okay"; - compatible = "adi,ad405x-adc"; + compatible = "adi,ad4052-adc"; channel@0 { reg = <0>; diff --git a/cmake/linker_script/common/common-rom.cmake b/cmake/linker_script/common/common-rom.cmake index a6d3e09bf2644..91795e82fed1e 100644 --- a/cmake/linker_script/common/common-rom.cmake +++ b/cmake/linker_script/common/common-rom.cmake @@ -153,6 +153,10 @@ if(CONFIG_SENSOR_ASYNC_API) zephyr_iterable_section(NAME sensor_decoder_api KVMA RAM_REGION GROUP RODATA_REGION) endif() +if(CONFIG_ADC_STREAM) + zephyr_iterable_section(NAME adc_decoder_api KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN ${CONFIG_LINKER_ITERABLE_SUBALIGN}) +endif() + if(CONFIG_MCUMGR) zephyr_iterable_section(NAME mcumgr_handler KVMA RAM_REGION GROUP RODATA_REGION) endif() diff --git a/drivers/adc/CMakeLists.txt b/drivers/adc/CMakeLists.txt index 571318ac56d39..b6d5cd7f77849 100644 --- a/drivers/adc/CMakeLists.txt +++ b/drivers/adc/CMakeLists.txt @@ -65,6 +65,7 @@ zephyr_library_sources_ifdef(CONFIG_ADC_MAX32 adc_max32.c) zephyr_library_sources_ifdef(CONFIG_ADC_AD4114 adc_ad4114.c) zephyr_library_sources_ifdef(CONFIG_ADC_AD7124 adc_ad7124.c) zephyr_library_sources_ifdef(CONFIG_ADC_AD405X adc_ad405x.c) +zephyr_library_sources_ifdef(CONFIG_ADC_STREAM default_rtio_adc.c) zephyr_library_sources_ifdef(CONFIG_ADC_AD4130 adc_ad4130.c) zephyr_library_sources_ifdef(CONFIG_ADC_REALTEK_RTS5912 adc_realtek_rts5912.c) zephyr_library_sources_ifdef(CONFIG_ADC_TI_AM335X adc_ti_am335x.c) diff --git a/drivers/adc/Kconfig b/drivers/adc/Kconfig index 7f5263af515f4..9145db22fa7af 100644 --- a/drivers/adc/Kconfig +++ b/drivers/adc/Kconfig @@ -50,6 +50,15 @@ config ADC_INIT_PRIORITY help ADC driver device initialization priority. +config ADC_STREAM + bool "ADC stream support" + select RTIO + select RTIO_SYS_MEM_BLOCKS + select RTIO_CONSUME_SEM + select RTIO_WORKQ + help + This option enables the stream API calls. + module = ADC module-str = ADC source "subsys/logging/Kconfig.template.log_config" diff --git a/drivers/adc/Kconfig.ad405x b/drivers/adc/Kconfig.ad405x index 5abc89a8dd56f..7d804bab44c9a 100644 --- a/drivers/adc/Kconfig.ad405x +++ b/drivers/adc/Kconfig.ad405x @@ -11,6 +11,13 @@ config ADC_AD405X help Enable ADC driver for ADI AD405X. +config AD405X_STREAM + bool "Use FIFO to stream data" + select AD405X_TRIGGER + depends on SPI_RTIO + help + Use this configuration option to enable streaming ADC data via RTIO. + config AD405X_TRIGGER bool "AD405X interrupts" default n diff --git a/drivers/adc/Kconfig.max32 b/drivers/adc/Kconfig.max32 index b38059c203519..7bdc2de728a24 100644 --- a/drivers/adc/Kconfig.max32 +++ b/drivers/adc/Kconfig.max32 @@ -10,3 +10,10 @@ config ADC_MAX32 select PINCTRL help Enable ADC driver for ADI MAX32xxx MCUs. + +config ADC_MAX32_STREAM + bool "Use FIFO to stream data" + select ADC_ASYNC + depends on HAS_ADC_MAX32_REVB_ME18 + help + Use this configuration option to enable streaming ADC data via RTIO. diff --git a/drivers/adc/adc_ad405x.c b/drivers/adc/adc_ad405x.c index d45355781e12f..8383c8d05a01f 100644 --- a/drivers/adc/adc_ad405x.c +++ b/drivers/adc/adc_ad405x.c @@ -73,6 +73,8 @@ LOG_MODULE_REGISTER(adc_ad405x, CONFIG_ADC_LOG_LEVEL); #define AD405X_SINGLE_ENDED 0x0U #define AD405X_DIFFERENTIAL BIT(7) +#define AD405X_NO_GPIO 0xFFU + /** AD405X_REG_TIMER_CONFIG Bit Definitions */ #define AD405X_FS_BURST_AUTO_MSK GENMASK(7, 4) @@ -151,6 +153,9 @@ struct adc_ad405x_config { struct gpio_dt_spec conversion; uint16_t chip_id; const struct adc_dt_spec spec; +#ifdef CONFIG_AD405X_STREAM + uint32_t sampling_period; +#endif /* CONFIG_AD405X_STREAM */ }; struct adc_ad405x_data { @@ -174,9 +179,94 @@ struct adc_ad405x_data { struct gpio_callback gpio0_cb; struct k_sem sem_drdy; uint8_t has_drdy; +#endif /* CONFIG_AD405X_TRIGGER */ +#ifdef CONFIG_AD405X_STREAM + struct rtio_iodev_sqe *sqe; + struct rtio *rtio_ctx; + struct rtio_iodev *iodev; + uint64_t timestamp; + struct rtio *r_cb; + uint32_t adc_sample; + uint8_t data_ready_gpio; +#if DT_HAS_CHOSEN(zephyr_adc_clock) + const struct device *timer_dev; +#else + struct k_timer sample_timer; #endif +#endif /* CONFIG_AD405X_STREAM */ +}; + +#ifdef CONFIG_AD405X_STREAM +#include + +#define AD405X_DEF_SAMPLING_PERIOD 10000U /* 10ms */ + +/** AD405X qscale modes */ +enum ad405x_qscale_modes { + AD4050_6_12B_MODE = 0, + AD4050_6_14B_MODE = 1, + AD4052_8_16B_MODE = 2, + AD4052_8_20B_MODE = 3, }; +struct adc_ad405x_fifo_data { + uint8_t is_fifo: 1; + uint8_t ad405x_qscale_mode: 2; + uint8_t diff_mode: 1; + uint8_t empty: 1; + uint8_t res: 3; + uint16_t vref_mv; + uint64_t timestamp; +} __attribute__((__packed__)); + +static void ad405x_stream_irq_handler(const struct device *dev); +static int ad405x_conv_start(const struct device *dev); + +#if DT_HAS_CHOSEN(zephyr_adc_clock) +static void timer_alarm_handler(const struct device *counter_dev, uint8_t chan_id, + uint32_t ticks, void *user_data) +{ + struct adc_ad405x_data *data = (struct adc_ad405x_data *)user_data; + + ad405x_conv_start(data->dev); +} +#else +static void sample_timer_handler(struct k_timer *timer) +{ + struct adc_ad405x_data *data = CONTAINER_OF(timer, struct adc_ad405x_data, + sample_timer); + + ad405x_conv_start(data->dev); +} +#endif +static void ad405x_timer_init(const struct device *dev) +{ + struct adc_ad405x_data *data = dev->data; + +#if DT_HAS_CHOSEN(zephyr_adc_clock) + counter_start(data->timer_dev); +#else + k_timer_init(&data->sample_timer, sample_timer_handler, NULL); +#endif +} + +static void ad405x_timer_start(const struct device *dev) +{ + struct adc_ad405x_data *data = dev->data; + const struct adc_ad405x_config *cfg_405 = (const struct adc_ad405x_config *)dev->config; +#if DT_HAS_CHOSEN(zephyr_adc_clock) + struct counter_alarm_cfg alarm_cfg = {.flags = 0, + .ticks = counter_us_to_ticks(data->timer_dev, (uint64_t)cfg_405->sampling_period), + .callback = timer_alarm_handler, + .user_data = data}; + + counter_set_channel_alarm(data->timer_dev, 0, &alarm_cfg); +#else + k_timer_start(&data->sample_timer, K_USEC(cfg_405->sampling_period), K_NO_WAIT); +#endif +} +#endif /* CONFIG_AD405X_STREAM */ + static bool ad405x_bus_is_ready_spi(const union ad405x_bus *bus) { bool ret = spi_is_ready_dt(&bus->spi); @@ -310,7 +400,17 @@ static void ad405x_gpio1_callback(const struct device *dev, struct gpio_callback k_sem_give(&drv_data->sem_devrdy); break; case AD405X_DATA_READY: +#ifdef CONFIG_AD405X_STREAM + const struct adc_read_config *cfg_adc = drv_data->sqe->sqe.iodev->data; + + if (cfg_adc->is_streaming) { + ad405x_stream_irq_handler(drv_data->dev); + } else { + k_sem_give(&drv_data->sem_drdy); + } +#else k_sem_give(&drv_data->sem_drdy); +#endif /* CONFIG_AD405X_STREAM */ gpio_flag = GPIO_INT_EDGE_TO_INACTIVE; break; default: /* TODO */ @@ -331,7 +431,17 @@ static void ad405x_gpio0_callback(const struct device *dev, struct gpio_callback switch (drv_data->gp0_mode) { case AD405X_DATA_READY: +#ifdef CONFIG_AD405X_STREAM + const struct adc_read_config *cfg_adc = drv_data->sqe->sqe.iodev->data; + + if (cfg_adc->is_streaming) { + ad405x_stream_irq_handler(drv_data->dev); + } else { + k_sem_give(&drv_data->sem_drdy); + } +#else k_sem_give(&drv_data->sem_drdy); +#endif /* CONFIG_AD405X_STREAM */ gpio_flag = GPIO_INT_EDGE_TO_INACTIVE; break; default: @@ -459,7 +569,7 @@ static int adc_ad405x_validate_buffer_size(const struct device *dev, return 0; } -int ad405x_conv_start(const struct device *dev) +static int ad405x_conv_start(const struct device *dev) { const struct adc_ad405x_config *cfg = dev->config; int ret; @@ -701,12 +811,18 @@ int ad405x_set_gpx_mode(const struct device *dev, uint8_t gp0_1, enum ad405x_gpx if (gpx_mode == AD405X_DATA_READY) { gpio_pin_interrupt_configure_dt(&cfg->gp0_interrupt, GPIO_INT_EDGE_TO_INACTIVE); +#ifdef CONFIG_AD405X_STREAM + data->data_ready_gpio = AD405X_GP0; +#endif /* CONFIG_AD405X_STREAM */ } data->gp0_mode = gpx_mode; } else { if (gpx_mode == AD405X_DATA_READY) { gpio_pin_interrupt_configure_dt(&cfg->gp1_interrupt, GPIO_INT_EDGE_TO_INACTIVE); +#ifdef CONFIG_AD405X_STREAM + data->data_ready_gpio = AD405X_GP1; +#endif /* CONFIG_AD405X_STREAM */ } data->gp1_mode = gpx_mode; } @@ -899,17 +1015,322 @@ static int adc_ad405x_init(const struct device *dev) return ret; } +#ifdef CONFIG_AD405X_STREAM +void ad405x_submit_stream(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe) +{ + struct adc_ad405x_data *data = (struct adc_ad405x_data *)dev->data; + const struct adc_ad405x_config *cfg_405 = (const struct adc_ad405x_config *)dev->config; + + if (data->data_ready_gpio > AD405X_GP1) { + LOG_ERR("DATA_READY irq is not enabled!"); + rtio_iodev_sqe_err(iodev_sqe, -ENOTSUP); + return; + } + + int rc; + + if (data->data_ready_gpio == AD405X_GP0) { + rc = gpio_pin_interrupt_configure_dt(&cfg_405->gp0_interrupt, + GPIO_INT_DISABLE); + } else { + rc = gpio_pin_interrupt_configure_dt(&cfg_405->gp1_interrupt, + GPIO_INT_DISABLE); + } + + if (rc < 0) { + rtio_iodev_sqe_err(iodev_sqe, rc); + return; + } + + if (data->operation_mode == AD405X_CONFIG_MODE_OP) { + rc = ad405x_set_operation_mode(dev, cfg_405->active_mode); + + if (rc < 0) { + LOG_ERR("Set operation mode failed!"); + return; + } + ad405x_timer_init(dev); + } + + if (data->data_ready_gpio == AD405X_GP0) { + rc = gpio_pin_interrupt_configure_dt(&cfg_405->gp0_interrupt, + GPIO_INT_EDGE_TO_INACTIVE); + } else { + rc = gpio_pin_interrupt_configure_dt(&cfg_405->gp1_interrupt, + GPIO_INT_EDGE_TO_INACTIVE); + } + + if (rc < 0) { + rtio_iodev_sqe_err(iodev_sqe, rc); + return; + } + + data->sqe = iodev_sqe; + ad405x_timer_start(dev); +} + +static const uint32_t adc_ad405x_resolution[] = { + [AD4050_6_12B_MODE] = 12, + [AD4050_6_14B_MODE] = 14, + [AD4052_8_16B_MODE] = 16, + [AD4052_8_20B_MODE] = 20, +}; + +static inline void adc_ad405x_convert_q31(q31_t *out, const uint8_t *buff, + enum ad405x_qscale_modes mode, uint8_t diff_mode, + uint16_t vref_mv, uint8_t adc_shift) +{ + int32_t data_in = 0; + uint32_t scale = BIT(adc_ad405x_resolution[mode]); + + /* In Differential mode, 1 bit is used for sign */ + if (diff_mode) { + scale = BIT(adc_ad405x_resolution[mode] - 1); + } + + uint32_t sensitivity = (vref_mv * (scale - 1)) / scale + * 1000 / scale; /* uV / LSB */ + + switch (mode) { + case AD4050_6_12B_MODE: + case AD4050_6_14B_MODE: + case AD4052_8_16B_MODE: + data_in = sys_get_be16(buff); + if (diff_mode && (data_in & (BIT(adc_ad405x_resolution[mode] - 1)))) { + data_in |= ~BIT_MASK(adc_ad405x_resolution[mode]); + } + break; + + case AD4052_8_20B_MODE: + data_in = sys_get_be24(buff); + if (diff_mode && (data_in & (BIT(adc_ad405x_resolution[mode] - 1)))) { + data_in |= ~BIT_MASK(adc_ad405x_resolution[mode]); + } + break; + + default: + data_in = sys_get_be16(buff); + break; + } + + *out = BIT(31 - adc_shift)/* scaling to q_31*/ * sensitivity / 1000000/*uV to V*/ * data_in; +} + +static int ad405x_decoder_get_frame_count(const uint8_t *buffer, uint32_t channel, + uint16_t *frame_count) +{ + const struct adc_ad405x_fifo_data *enc_data = (const struct adc_ad405x_fifo_data *)buffer; + + if (enc_data->empty) { + return -ENODATA; + } + + /* This adc does not have FIFO so it will stream one sample at a time */ + *frame_count = 1; + + return 0; +} + +static int ad405x_decoder_decode(const uint8_t *buffer, uint32_t channel, uint32_t *fit, + uint16_t max_count, void *data_out) +{ + const struct adc_ad405x_fifo_data *enc_data = (const struct adc_ad405x_fifo_data *)buffer; + + if (*fit > 0) { + return -ENOTSUP; + } + + struct adc_data *data = (struct adc_data *)data_out; + + memset(data, 0, sizeof(struct adc_data)); + + if (enc_data->empty) { + data->header.base_timestamp_ns = 0; + data->header.reading_count = 0; + return -ENODATA; + } + + data->header.base_timestamp_ns = enc_data->timestamp; + data->header.reading_count = 1; + + /* 32 is used because input parameter for __builtin_clz func is + * unsigneg int (32 bits) and func will consider any input value + * as 32 bit. + */ + data->shift = 32 - __builtin_clz(enc_data->vref_mv); + + buffer += sizeof(struct adc_ad405x_fifo_data); + + data->readings[0].timestamp_delta = 0; + adc_ad405x_convert_q31(&data->readings[0].value, buffer, enc_data->ad405x_qscale_mode, + enc_data->diff_mode, enc_data->vref_mv, data->shift); + + *fit = 1; + + return 0; +} + +static void ad405x_process_sample_cb(struct rtio *r, const struct rtio_sqe *sqe, void *arg) +{ + struct rtio_iodev_sqe *iodev_sqe = sqe->userdata; + + rtio_iodev_sqe_ok(iodev_sqe, 0); +} + +static void ad405x_stream_irq_handler(const struct device *dev) +{ + struct adc_ad405x_data *data = (struct adc_ad405x_data *)dev->data; + const struct adc_ad405x_config *cfg = (const struct adc_ad405x_config *)dev->config; + struct rtio_iodev_sqe *current_sqe = data->sqe; + uint32_t sample_size = 2; + enum ad405x_qscale_modes qscale_mode = AD4050_6_12B_MODE; + struct adc_read_config *read_config = (struct adc_read_config *)data->sqe->sqe.iodev->data; + + if (read_config == NULL) { + return; + } + + if (current_sqe == NULL) { + return; + } + + if (cfg->chip_id == AD4050_CHIP_ID) { + if ((cfg->active_mode == AD405X_BURST_AVERAGING_MODE_OP) + || (cfg->active_mode == AD405X_AVERAGING_MODE_OP)) { + qscale_mode = AD4050_6_14B_MODE; + } + } else { /* AD4052_CHIP_ID */ + if ((cfg->active_mode == AD405X_BURST_AVERAGING_MODE_OP) + || (cfg->active_mode == AD405X_AVERAGING_MODE_OP)) { + sample_size = 3; + qscale_mode = AD4052_8_20B_MODE; + } else { + qscale_mode = AD4052_8_16B_MODE; + } + } + +#if DT_HAS_CHOSEN(zephyr_adc_clock) + uint32_t ticks; + int ret = counter_get_value(data->timer_dev, &ticks); + + if (ret != 0) { + LOG_ERR("Failed to get timer value"); + data->timestamp = 0; + return; + } + + data->timestamp = (uint64_t)(counter_ticks_to_us(data->timer_dev, ticks)) + * 1000 /* uS to nS */; +#else + data->timestamp = k_ticks_to_ns_floor64(k_uptime_ticks()); +#endif + data->sqe = NULL; + + /* Not inherently an underrun/overrun as we may have a buffer to fill next time */ + if (current_sqe == NULL) { + LOG_ERR("No pending SQE"); + return; + } + + const size_t min_read_size = sizeof(struct adc_ad405x_fifo_data) + sample_size; + + uint8_t *buf; + uint32_t buf_len; + + if (rtio_sqe_rx_buf(current_sqe, min_read_size, min_read_size, &buf, &buf_len) != 0) { + rtio_iodev_sqe_err(current_sqe, -ENOMEM); + return; + } + + /* Read FIFO and call back to rtio with rtio_sqe completion */ + struct adc_ad405x_fifo_data *hdr = (struct adc_ad405x_fifo_data *)buf; + + hdr->is_fifo = 1; + hdr->timestamp = data->timestamp; + hdr->diff_mode = cfg->spec.channel_cfg.differential; + hdr->vref_mv = cfg->spec.vref_mv; + hdr->ad405x_qscale_mode = qscale_mode; + + uint8_t *read_buf = buf + sizeof(*hdr); + + if (read_config->trigger_cnt != 0) { + enum adc_stream_data_opt data_opt = read_config->triggers[0].opt; + + for (int i = 1; i < read_config->trigger_cnt; i++) { + data_opt = MIN(data_opt, read_config->triggers[i].opt); + } + + if (data_opt == ADC_STREAM_DATA_NOP || data_opt == ADC_STREAM_DATA_DROP) { + hdr->empty = 1; + } + } + + /* Setup new rtio chain to read the data and report then check the result */ + struct rtio_sqe *read_fifo_data = rtio_sqe_acquire(data->rtio_ctx); + struct rtio_sqe *complete_op = rtio_sqe_acquire(data->rtio_ctx); + + rtio_sqe_prep_read(read_fifo_data, data->iodev, RTIO_PRIO_NORM, read_buf, sample_size, + current_sqe); + read_fifo_data->flags = RTIO_SQE_CHAINED; + rtio_sqe_prep_callback(complete_op, ad405x_process_sample_cb, (void *)dev, current_sqe); + + rtio_submit(data->rtio_ctx, 0); +} + +static bool ad405x_decoder_has_trigger(const uint8_t *buffer, enum adc_trigger_type trigger) +{ + const struct adc_ad405x_fifo_data *data = (const struct adc_ad405x_fifo_data *)buffer; + + if (!data->is_fifo) { + return false; + } + + switch (trigger) { + /* This family of chips doesn't have FIFO so if there is a buffer trigger has happen. */ + case ADC_TRIG_DATA_READY: + case ADC_TRIG_FIFO_WATERMARK: + case ADC_TRIG_FIFO_FULL: + return true; + default: + return false; + } +} + +ADC_DECODER_API_DT_DEFINE() = { + .get_frame_count = ad405x_decoder_get_frame_count, + .decode = ad405x_decoder_decode, + .has_trigger = ad405x_decoder_has_trigger, +}; + +int ad405x_get_decoder(const struct device *dev, const struct adc_decoder_api **api) +{ + ARG_UNUSED(dev); + *api = &ADC_DECODER_NAME(); + + return 0; +} + +#endif /* CONFIG_AD405X_STREAM */ + static DEVICE_API(adc, ad405x_api_funcs) = { .channel_setup = ad405x_channel_setup, .read = ad405x_read, .ref_internal = 2500, #ifdef CONFIG_ADC_ASYNC .read_async = ad405x_adc_read_async, -#endif +#endif /* CONFIG_ADC_ASYNC */ +#ifdef CONFIG_AD405X_STREAM + .submit = ad405x_submit_stream, + .get_decoder = ad405x_get_decoder, +#endif /* CONFIG_AD405X_STREAM */ }; #define AD405X_SPI_CFG SPI_WORD_SET(8) | SPI_TRANSFER_MSB +#define AD405X_RTIO_DEFINE(inst) \ + SPI_DT_IODEV_DEFINE(ad405x_iodev_##inst, DT_DRV_INST(inst), AD405X_SPI_CFG, 0U); \ + RTIO_DEFINE(ad405x_rtio_ctx_##inst, 16, 16); + #define DT_INST_AD405X(inst, t) DT_INST(inst, adi_ad##t##_adc) #define AD405X_GPIO_PROPS1(n) \ @@ -927,17 +1348,27 @@ static DEVICE_API(adc, ad405x_api_funcs) = { (AD405X_GPIO_PROPS0(n))) #define AD405X_INIT(t, n) \ - static struct adc_ad405x_data ad##t##_data_##n = {}; \ - static const struct adc_ad405x_config ad##t##_config_##n = { \ - .bus = {.spi = SPI_DT_SPEC_GET(DT_INST_AD405X(n, t), AD405X_SPI_CFG, 0)}, \ - .conversion = GPIO_DT_SPEC_GET_BY_IDX(DT_INST_AD405X(n, t), conversion_gpios, 0),\ - IF_ENABLED(CONFIG_AD405X_TRIGGER, (AD405X_GPIO(t, n))) \ - .chip_id = t, \ - .active_mode = AD405X_SAMPLE_MODE_OP, \ - .spec = ADC_DT_SPEC_STRUCT(DT_INST(n, DT_DRV_COMPAT), 0) \ - }; \ - DEVICE_DT_DEFINE(DT_INST_AD405X(n, t), adc_ad405x_init, NULL, &ad##t##_data_##n, \ - &ad##t##_config_##n, POST_KERNEL, \ + IF_ENABLED(CONFIG_AD405X_STREAM, (AD405X_RTIO_DEFINE(n))); \ + static struct adc_ad405x_data ad##t##_data_##n = { \ + IF_ENABLED(CONFIG_AD405X_STREAM, (.rtio_ctx = &ad405x_rtio_ctx_##n, \ + .iodev = &ad405x_iodev_##n, .data_ready_gpio = AD405X_NO_GPIO, \ + COND_CODE_1(DT_HAS_CHOSEN(zephyr_adc_clock), \ + (.timer_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_adc_clock)),), \ + ()))) \ + }; \ + static const struct adc_ad405x_config ad##t##_config_##n = { \ + .bus = {.spi = SPI_DT_SPEC_GET(DT_INST_AD405X(n, t), AD405X_SPI_CFG, 0)}, \ + .conversion = GPIO_DT_SPEC_GET_BY_IDX(DT_INST_AD405X(n, t), \ + conversion_gpios, 0), \ + IF_ENABLED(CONFIG_AD405X_TRIGGER, (AD405X_GPIO(t, n))) \ + .chip_id = t, \ + .active_mode = AD405X_SAMPLE_MODE_OP, \ + .spec = ADC_DT_SPEC_STRUCT(DT_INST(n, DT_DRV_COMPAT), 0), \ + IF_ENABLED(CONFIG_AD405X_STREAM, (.sampling_period = \ + DT_INST_PROP_OR(n, sampling_period, AD405X_DEF_SAMPLING_PERIOD),)) \ + }; \ + DEVICE_DT_DEFINE(DT_INST_AD405X(n, t), adc_ad405x_init, NULL, &ad##t##_data_##n, \ + &ad##t##_config_##n, POST_KERNEL, \ CONFIG_ADC_INIT_PRIORITY, &ad405x_api_funcs); /* diff --git a/drivers/adc/adc_max32.c b/drivers/adc/adc_max32.c index af04b00f573ae..3d0cb38f51486 100644 --- a/drivers/adc/adc_max32.c +++ b/drivers/adc/adc_max32.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include LOG_MODULE_REGISTER(adc_max32, CONFIG_ADC_LOG_LEVEL); @@ -24,6 +26,16 @@ LOG_MODULE_REGISTER(adc_max32, CONFIG_ADC_LOG_LEVEL); /* reference voltage for the ADC */ #define MAX32_ADC_VREF_MV DT_INST_PROP(0, vref_mv) +#define ADC_MAX32_INT_FIFO_LVL_MSK BIT(7) +#define ADC_MAX32_SAMPLE_SIZE 2 +#define ADC_MAX32_BYTE_COUNT 16 + +enum adc_max32_fifo_format { + ADC_MAX32_DATA_STATUS_FIFO, + ADC_MAX32_DATA_ONLY_FIFO, + ADC_MAX32_RAW_DATA_ONLY_FIFO, +}; + struct max32_adc_config { uint8_t channel_count; mxc_adc_regs_t *regs; @@ -44,8 +56,45 @@ struct max32_adc_data { uint32_t channels; uint32_t sample_channels; const uint8_t resolution; +#ifdef CONFIG_ADC_MAX32_STREAM + struct rtio_iodev_sqe *sqe; + struct rtio *rtio_ctx; + struct rtio_iodev *iodev; + uint64_t timestamp; + struct rtio *r_cb; + uint32_t adc_sample; + uint8_t data_ready_gpio; + uint8_t no_mem; + struct k_timer sample_timer; + const struct adc_sequence *sequence; + uint8_t fifo_full_irq; +#endif /* CONFIG_ADC_MAX32_STREAM */ +}; + + +#ifdef CONFIG_ADC_MAX32_STREAM +/** MAX32 qscale modes */ +enum max32_qscale_modes { + MAX32_12B_MODE = 0, +}; + +struct adc_max32_fifo_config { + enum adc_max32_fifo_format fifo_format; + uint16_t fifo_samples; }; +struct adc_max32_fifo_data { + uint16_t is_fifo: 1; + uint16_t max32_qscale_mode: 1; + uint16_t diff_mode: 1; + uint16_t res: 4; + uint16_t fifo_byte_count: 5; + uint16_t sample_set_size: 4; + uint16_t vref_mv; + uint64_t timestamp; +} __attribute__((__packed__)); +#endif /* CONFIG_ADC_MAX32_STREAM */ + #ifdef CONFIG_ADC_ASYNC static void adc_complete_cb(void *req, int error) { @@ -54,6 +103,16 @@ static void adc_complete_cb(void *req, int error) } #endif /* CONFIG_ADC_ASYNC */ +#ifdef CONFIG_ADC_MAX32_STREAM +static void adc_complete_rtio_cb(const struct device *dev) +{ + struct max32_adc_data *data = dev->data; + struct rtio_iodev_sqe *iodev_sqe = data->sqe; + + rtio_iodev_sqe_ok(iodev_sqe, 0); +} +#endif /* CONFIG_ADC_MAX32_STREAM */ + static void adc_max32_start_channel(const struct device *dev) { struct max32_adc_data *data = dev->data; @@ -150,6 +209,159 @@ static int adc_max32_read(const struct device *dev, const struct adc_sequence *s return ret; } +#ifdef CONFIG_ADC_MAX32_STREAM +static int start_read_stream(const struct device *dev, const struct adc_sequence *seq) +{ + struct max32_adc_data *data = dev->data; + int ret = 0; + + if (seq->resolution != data->resolution) { + LOG_ERR("Unsupported resolution (%d)", seq->resolution); + return -ENOTSUP; + } + if (seq->channels == 0) { + return -EINVAL; + } + if ((data->channels & seq->channels) != seq->channels) { + return -EINVAL; + } + + ret = Wrap_MXC_ADC_AverageConfig(seq->oversampling); + if (ret != 0) { + return -EINVAL; + } + + data->ctx.asynchronous = 1; + data->sample_channels = seq->channels; + + /* Here we use regular cb that does nothing, it should be + * adc_complete_rtio_cb but the problem is dev struct + * cannot be passed to HAL without some big changes + * in the HAL layers, for now it is set like this + */ + ret = Wrap_MXC_ADC_StartConversionAsyncStream(&data->sample_channels, adc_complete_cb); + if (ret != 0) { + return -EINVAL; + } + + return adc_context_wait_for_completion(&data->ctx); +} + +void adc_max32_submit_stream(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe) +{ + struct max32_adc_data *data = (struct max32_adc_data *)dev->data; + const struct adc_read_config *read_cfg = iodev_sqe->sqe.iodev->data; + int rc; + + if (data->no_mem == 1) { + data->no_mem = 0; + return; + } + data->sqe = iodev_sqe; + + adc_context_lock(&data->ctx, false, NULL); + rc = start_read_stream(dev, read_cfg->sequence); + + adc_context_release(&data->ctx, rc); + + if (rc < 0) { + LOG_ERR("Error starting conversion (%d)", rc); + } +} + +static const uint32_t adc_max32_resolution[] = { + [MAX32_12B_MODE] = 12, +}; + +static inline int adc_max32_convert_q31(q31_t *out, const uint8_t *buff, + enum max32_qscale_modes mode, uint8_t diff_mode, + uint16_t vref_mv, uint8_t adc_shift) +{ + int32_t data_in = 0; + uint32_t scale = BIT(adc_max32_resolution[mode]); + + /* No Differential mode */ + if (diff_mode) { + return -EINVAL; + } + + uint32_t sensitivity = (vref_mv * (scale - 1)) / scale + * 1000 / scale; /* uV / LSB */ + + if (mode == MAX32_12B_MODE) { + data_in = (buff[1] << 8) | buff[0]; + if (diff_mode && (data_in & (BIT(adc_max32_resolution[mode] - 1)))) { + data_in |= ~BIT_MASK(adc_max32_resolution[mode]); + } + } else { + data_in = sys_get_be16(buff); + } + + *out = BIT(31 - adc_shift) * sensitivity / 1000000 * data_in; + return 0; +} + +static int adc_max32_decoder_get_frame_count(const uint8_t *buffer, uint32_t channel, + uint16_t *frame_count) +{ + const struct adc_max32_fifo_data *data = (const struct adc_max32_fifo_data *)buffer; + + *frame_count = data->fifo_byte_count/ADC_MAX32_SAMPLE_SIZE; + + return 0; +} + +static int adc_max32_decoder_decode(const uint8_t *buffer, uint32_t channel, uint32_t *fit, + uint16_t max_count, void *data_out) +{ + const struct adc_max32_fifo_data *enc_data = (const struct adc_max32_fifo_data *)buffer; + const uint8_t *buffer_end = + buffer + sizeof(struct adc_max32_fifo_data) + enc_data->fifo_byte_count; + int count = 0; + uint8_t sample_num = 0; + + if (buffer_end <= (buffer + *fit + sizeof(struct adc_max32_fifo_data))) { + return 0; + } + + struct adc_data *data = (struct adc_data *)data_out; + + memset(data, 0, sizeof(struct adc_data)); + data->header.base_timestamp_ns = enc_data->timestamp; + data->header.reading_count = 1; + + /* 32 is used because input parameter for __builtin_clz func is + * unsigneg int (32 bits) and func will consider any input value + * as 32 bit. + */ + data->shift = 32 - __builtin_clz(enc_data->vref_mv); + + buffer += sizeof(struct adc_max32_fifo_data); + uint8_t sample_set_size = enc_data->sample_set_size; + /* Calculate which sample is decoded. */ + if (*fit) { + sample_num = *fit / sample_set_size; + } + + while (count < max_count && buffer < buffer_end) { + /* 125 KSPS - this can be calculated from + * cnt and idle dts parameters but it is hardcoded for now + */ + data->readings[count].timestamp_delta = sample_num * (UINT32_C(1000000000) / 62500); + adc_max32_convert_q31(&data->readings[count].value, (buffer + *fit), + enc_data->max32_qscale_mode, enc_data->diff_mode, + enc_data->vref_mv, data->shift); + + sample_num++; + *fit += sample_set_size; + count++; + } + + return 0; + +} +#endif /* CONFIG_ADC_MAX32_STREAM */ + #ifdef CONFIG_ADC_ASYNC static int adc_max32_read_async(const struct device *dev, const struct adc_sequence *seq, struct k_poll_signal *async) @@ -276,6 +488,73 @@ static int adc_max32_init(const struct device *dev) return 0; } +#ifdef CONFIG_ADC_MAX32_STREAM +static void adc_max32_rtio_isr(const struct device *dev) +{ + struct max32_adc_data *const data = dev->data; + uint32_t flags = MXC_ADC_GetFlags(); + uint32_t int_req = BIT(3); + + MXC_ADC_Handler(); + if (flags & int_req) { + MXC_ADC_Free(); + } + MXC_ADC_ClearFlags(flags); + + if (flags & WRAP_MXC_F_ADC_CONV_DONE_IF) { + + data->timestamp = k_ticks_to_ns_floor64(k_uptime_ticks()); + + const size_t min_read_size = 64; + + uint8_t *buf; + uint32_t buf_len; + + if (rtio_sqe_rx_buf(data->sqe, min_read_size, min_read_size, + &buf, &buf_len) != 0) { + data->no_mem = 1; + rtio_iodev_sqe_err(data->sqe, -ENOMEM); + return; + } + struct adc_max32_fifo_data *hdr = (struct adc_max32_fifo_data *)buf; + + hdr->is_fifo = 1; + hdr->timestamp = data->timestamp; + hdr->vref_mv = MAX32_ADC_VREF_MV; + hdr->max32_qscale_mode = MAX32_12B_MODE; + hdr->fifo_byte_count = ADC_MAX32_BYTE_COUNT; + hdr->sample_set_size = ADC_MAX32_SAMPLE_SIZE; + + uint8_t *read_buf = buf + sizeof(*hdr); + + Wrap_MXC_ADC_GetData((uint16_t **)&read_buf); + + if (data->sample_channels != 0) { + adc_max32_start_channel(dev); + } else { + Wrap_MXC_ADC_DisableConversion(); + adc_context_on_sampling_done(&data->ctx, dev); + } + } + if (flags & int_req) { + + adc_complete_rtio_cb(dev); + } +} + +ADC_DECODER_API_DT_DEFINE() = { + .get_frame_count = adc_max32_decoder_get_frame_count, + .decode = adc_max32_decoder_decode, +}; + +int adc_max32_get_decoder(const struct device *dev, const struct adc_decoder_api **api) +{ + ARG_UNUSED(dev); + *api = &ADC_DECODER_NAME(); + + return 0; +} +#else static void adc_max32_isr(const struct device *dev) { struct max32_adc_data *const data = dev->data; @@ -295,6 +574,7 @@ static void adc_max32_isr(const struct device *dev) } } } +#endif /* CONFIG_ADC_MAX32_STREAM */ static DEVICE_API(adc, adc_max32_driver_api) = { .channel_setup = adc_max32_channel_setup, @@ -303,14 +583,21 @@ static DEVICE_API(adc, adc_max32_driver_api) = { .read_async = adc_max32_read_async, #endif /* CONFIG_ADC_ASYNC */ .ref_internal = MAX32_ADC_VREF_MV, +#ifdef CONFIG_ADC_MAX32_STREAM + .submit = adc_max32_submit_stream, + .get_decoder = adc_max32_get_decoder, +#endif /* CONFIG_ADC_MAX32_STREAM */ }; #define MAX32_ADC_INIT(_num) \ PINCTRL_DT_INST_DEFINE(_num); \ static void max32_adc_irq_init_##_num(void) \ { \ - IRQ_CONNECT(DT_INST_IRQN(_num), DT_INST_IRQ(_num, priority), adc_max32_isr, \ - DEVICE_DT_INST_GET(_num), 0); \ + COND_CODE_1(CONFIG_ADC_MAX32_STREAM, \ + (IRQ_CONNECT(DT_INST_IRQN(_num), DT_INST_IRQ(_num, priority), adc_max32_rtio_isr, \ + DEVICE_DT_INST_GET(_num), 0)), \ + (IRQ_CONNECT(DT_INST_IRQN(_num), DT_INST_IRQ(_num, priority), adc_max32_isr, \ + DEVICE_DT_INST_GET(_num), 0))); \ irq_enable(DT_INST_IRQN(_num)); \ }; \ static const struct max32_adc_config max32_adc_config_##_num = { \ diff --git a/drivers/adc/default_rtio_adc.c b/drivers/adc/default_rtio_adc.c new file mode 100644 index 0000000000000..63e735b081234 --- /dev/null +++ b/drivers/adc/default_rtio_adc.c @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2023 Google LLC. + * Copyright (c) 2024 Croxel Inc. + * Copyright (c) 2025 Analog Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +LOG_MODULE_REGISTER(adc_compat, CONFIG_ADC_LOG_LEVEL); + +static void adc_submit_fallback(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe); + +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); + } else if (!cfg->is_streaming) { + adc_submit_fallback(dev, iodev_sqe); + } 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; +} + +/** + * @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); +} + +/** + * @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, +}; diff --git a/dts/bindings/adc/adi,ad405x-adc-base.yaml b/dts/bindings/adc/adi,ad405x-adc-base.yaml index 7a6fc10a039b3..9079956b73a63 100644 --- a/dts/bindings/adc/adi,ad405x-adc-base.yaml +++ b/dts/bindings/adc/adi,ad405x-adc-base.yaml @@ -35,5 +35,10 @@ properties: This option increases the range from 0V to 2 x Vref. Note that this requires VDD >= 2 x Vref. + sampling-period: + type: int + description: | + Specify ADC sampling period in uS. + io-channel-cells: - input diff --git a/include/zephyr/drivers/adc.h b/include/zephyr/drivers/adc.h index b5c1cec256cf5..58ee9417e693e 100644 --- a/include/zephyr/drivers/adc.h +++ b/include/zephyr/drivers/adc.h @@ -16,6 +16,8 @@ #include #include #include +#include +#include #ifdef __cplusplus extern "C" { @@ -688,6 +690,169 @@ struct adc_sequence { bool calibrate; }; +struct adc_data_header { + /** + * The closest timestamp for when the first frame was generated as attained by + * :c:func:`k_uptime_ticks`. + */ + uint64_t base_timestamp_ns; + /** + * The number of elements in the 'readings' array. + * + * This must be at least 1 + */ + uint16_t reading_count; +}; + +/** + * Data for the adc channel. + */ +struct adc_data { + struct adc_data_header header; + int8_t shift; + struct adc_sample_data { + uint32_t timestamp_delta; + union { + q31_t value; + }; + } readings[1]; +}; + +/** + * @brief ADC trigger types. + */ +enum adc_trigger_type { + /** Trigger fires whenever new data is ready. */ + ADC_TRIG_DATA_READY, + + /** Trigger fires when the FIFO watermark has been reached. */ + ADC_TRIG_FIFO_WATERMARK, + + /** Trigger fires when the FIFO becomes full. */ + ADC_TRIG_FIFO_FULL, + + /** + * Number of all common adc triggers. + */ + ADC_TRIG_COMMON_COUNT, + + /** + * This and higher values are adc specific. + * Refer to the adc header file. + */ + ADC_TRIG_PRIV_START = ADC_TRIG_COMMON_COUNT, + + /** + * Maximum value describing a adc trigger type. + */ + ADC_TRIG_MAX = INT16_MAX, +}; + +/** + * @brief Options for what to do with the associated data when a trigger is consumed + */ +enum adc_stream_data_opt { + /** @brief Include whatever data is associated with the trigger */ + ADC_STREAM_DATA_INCLUDE = 0, + /** @brief Do nothing with the associated trigger data, it may be consumed later */ + ADC_STREAM_DATA_NOP = 1, + /** @brief Flush/clear whatever data is associated with the trigger */ + ADC_STREAM_DATA_DROP = 2, +}; + +struct adc_stream_trigger { + enum adc_trigger_type trigger; + enum adc_stream_data_opt opt; +}; + +/** + * @brief ADC Channel Specification + * + * A ADC channel specification is a unique identifier per ADC device describing + * a measurement channel. + * + */ +struct adc_chan_spec { + uint8_t chan_idx; /**< A ADC channel index */ + uint8_t chan_resolution; /**< A ADC channel resolution */ +}; + +/* + * Internal data structure used to store information about the IODevice for async reading and + * streaming adc data. + */ +struct adc_read_config { + const struct device *adc; + const bool is_streaming; + const struct adc_dt_spec *adc_spec; + const struct adc_stream_trigger *triggers; + struct adc_sequence *sequence; + uint16_t fifo_watermark_lvl; + uint16_t fifo_mode; + size_t adc_spec_cnt; + size_t trigger_cnt; +}; + +/** + * @brief Decodes a single raw data buffer + * + */ +struct adc_decoder_api { + /** + * @brief Get the number of frames in the current buffer. + * + * @param[in] buffer The buffer provided on the @ref rtio context. + * @param[in] channel The channel to get the count for + * @param[out] frame_count The number of frames on the buffer (at least 1) + * @return 0 on success + * @return -ENOTSUP if the channel/channel_idx aren't found + */ + int (*get_frame_count)(const uint8_t *buffer, uint32_t channel, + uint16_t *frame_count); + + /** + * @brief Get the size required to decode a given channel + * + * When decoding a single frame, use @p base_size. For every additional frame, add another + * @p frame_size. As an example, to decode 3 frames use: 'base_size + 2 * frame_size'. + * + * @param[in] adc_spec ADC Specs + * @param[in] channel The channel to query + * @param[out] base_size The size of decoding the first frame + * @param[out] frame_size The additional size of every additional frame + * @return 0 on success + * @return -ENOTSUP if the channel is not supported + */ + int (*get_size_info)(struct adc_dt_spec adc_spec, uint32_t channel, size_t *base_size, + size_t *frame_size); + + /** + * @brief Decode up to @p max_count samples from the buffer + * + * Decode samples of channel across multiple frames. If there exist + * multiple instances of the same channel, @p channel_index is used to differentiate them. + * + * @param[in] buffer The buffer provided on the @ref 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 + */ + int (*decode)(const uint8_t *buffer, uint32_t channel, uint32_t *fit, + uint16_t max_count, void *data_out); + + /** + * @brief Check if the given trigger type is present + * + * @param[in] buffer The buffer provided on the @ref rtio context + * @param[in] trigger The trigger type in question + * @return Whether the trigger is present in the buffer + */ + bool (*has_trigger)(const uint8_t *buffer, enum adc_trigger_type trigger); +}; /** * @brief Type definition of ADC API function for configuring a channel. @@ -703,6 +868,22 @@ typedef int (*adc_api_channel_setup)(const struct device *dev, typedef int (*adc_api_read)(const struct device *dev, const struct adc_sequence *sequence); +/** + * @brief Type definition of ADC API function for setting an submit + * stream request. + */ +typedef void (*adc_api_submit)(const struct device *dev, + struct rtio_iodev_sqe *sqe); + +/** + * @brief Get the decoder associate with the given device + * + * @see adc_get_decoder() for more details + */ +typedef int (*adc_api_get_decoder)(const struct device *dev, + const struct adc_decoder_api **api); + + /** * @brief Type definition of ADC API function for setting an asynchronous * read request. @@ -722,6 +903,10 @@ __subsystem struct adc_driver_api { adc_api_read read; #ifdef CONFIG_ADC_ASYNC adc_api_read_async read_async; +#endif +#ifdef CONFIG_ADC_STREAM + adc_api_submit submit; + adc_api_get_decoder get_decoder; #endif uint16_t ref_internal; /* mV */ }; @@ -834,6 +1019,21 @@ __syscall int adc_read_async(const struct device *dev, const struct adc_sequence *sequence, struct k_poll_signal *async); +/** + * @brief Get decoder APIs for that device. + * + * @note This function is available only if @kconfig{CONFIG_ADC_STREAM} + * is selected. + * + * @param dev Pointer to the device structure for the driver instance. + * @param api Pointer to the decoder which will be set upon success. + * + * @returns 0 on success, negative error code otherwise. + * + * + */ +__syscall int adc_get_decoder(const struct device *dev, + const struct adc_decoder_api **api); #ifdef CONFIG_ADC_ASYNC static inline int z_impl_adc_read_async(const struct device *dev, @@ -844,6 +1044,68 @@ static inline int z_impl_adc_read_async(const struct device *dev, } #endif /* CONFIG_ADC_ASYNC */ +#ifdef CONFIG_ADC_STREAM +/* + * Generic data structure used for encoding the sample timestamp and number of channels sampled. + */ +struct __attribute__((__packed__)) adc_data_generic_header { + /* The timestamp at which the data was collected from the adc */ + uint64_t timestamp_ns; + + /* + * The number of channels present in the frame. + */ + uint8_t num_channels; + + /* Shift value for all samples in the frame */ + int8_t shift; + + /* This padding is needed to make sure that the 'channels' field is aligned */ + int16_t _padding; + + /* Channels present in the frame */ + struct adc_chan_spec channels[0]; +}; + +static inline int adc_stream(struct rtio_iodev *iodev, struct rtio *ctx, void *userdata, + struct rtio_sqe **handle) +{ + if (IS_ENABLED(CONFIG_USERSPACE)) { + struct rtio_sqe sqe; + + rtio_sqe_prep_read_multishot(&sqe, iodev, RTIO_PRIO_NORM, userdata); + rtio_sqe_copy_in_get_handles(ctx, &sqe, handle, 1); + } else { + struct rtio_sqe *sqe = rtio_sqe_acquire(ctx); + + if (sqe == NULL) { + return -ENOMEM; + } + if (handle != NULL) { + *handle = sqe; + } + rtio_sqe_prep_read_multishot(sqe, iodev, RTIO_PRIO_NORM, userdata); + } + rtio_submit(ctx, 0); + return 0; +} + +static inline int z_impl_adc_get_decoder(const struct device *dev, + const struct adc_decoder_api **decoder) +{ + const struct adc_driver_api *api = DEVICE_API_GET(adc, dev); + + __ASSERT_NO_MSG(api != NULL); + + if (api->get_decoder == NULL) { + *decoder = NULL; + return -1; + } + + return api->get_decoder(dev, decoder); +} +#endif /* CONFIG_ADC_STREAM */ + /** * @brief Get the internal reference voltage. * @@ -1052,6 +1314,69 @@ static inline bool adc_is_ready_dt(const struct adc_dt_spec *spec) * @} */ +/** + * @brief Get the decoder name for the current driver + * + * This function depends on `DT_DRV_COMPAT` being defined. + */ +#define ADC_DECODER_NAME() UTIL_CAT(DT_DRV_COMPAT, __adc_decoder_api) + +/** + * @brief Statically get the decoder for a given node + * + * @code{.c} + * static const adc_decoder_api *decoder = ADC_DECODER_DT_GET(DT_ALIAS(adc)); + * @endcode + */ +#define ADC_DECODER_DT_GET(node_id) \ + &UTIL_CAT(DT_STRING_TOKEN_BY_IDX(node_id, compatible, 0), __adc_decoder_api) + +/** + * @brief Define a decoder API + * + * This macro should be created once per compatible string of a adc and will create a statically + * referenceable decoder API. + * + * @code{.c} + * ADC_DECODER_API_DT_DEFINE() = { + * .get_frame_count = my_driver_get_frame_count, + * .get_timestamp = my_driver_get_timestamp, + * .get_shift = my_driver_get_shift, + * .decode = my_driver_decode, + * }; + * @endcode + */ +#define ADC_DECODER_API_DT_DEFINE() \ + COND_CODE_1(DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT), (), (static)) \ + const STRUCT_SECTION_ITERABLE(adc_decoder_api, ADC_DECODER_NAME()) + +#define Z_MAYBE_ADC_DECODER_DECLARE_INTERNAL_IDX(node_id, prop, idx) \ + extern const struct adc_decoder_api UTIL_CAT( \ + DT_STRING_TOKEN_BY_IDX(node_id, prop, idx), __adc_decoder_api); + +#define Z_MAYBE_ADC_DECODER_DECLARE_INTERNAL(node_id) \ + COND_CODE_1(DT_NODE_HAS_PROP(node_id, compatible), \ + (DT_FOREACH_PROP_ELEM(node_id, compatible, \ + Z_MAYBE_ADC_DECODER_DECLARE_INTERNAL_IDX)), \ + ()) + +DT_FOREACH_STATUS_OKAY_NODE(Z_MAYBE_ADC_DECODER_DECLARE_INTERNAL) + +/* The default adc iodev API */ +extern const struct rtio_iodev_api __adc_iodev_api; + +#define ADC_DT_STREAM_IODEV(name, dt_node, adc_dt_spec, ...) \ + static struct adc_stream_trigger _CONCAT(__trigger_array_, name)[] = {__VA_ARGS__}; \ + static struct adc_read_config _CONCAT(__adc_read_config_, name) = { \ + .adc = DEVICE_DT_GET(dt_node), \ + .is_streaming = true, \ + .adc_spec = adc_dt_spec, \ + .triggers = _CONCAT(__trigger_array_, name), \ + .adc_spec_cnt = ARRAY_SIZE(adc_dt_spec), \ + .trigger_cnt = ARRAY_SIZE(_CONCAT(__trigger_array_, name)), \ + }; \ + RTIO_IODEV_DEFINE(name, &__adc_iodev_api, &_CONCAT(__adc_read_config_, name)) + #ifdef __cplusplus } #endif diff --git a/include/zephyr/linker/common-rom/common-rom-misc.ld b/include/zephyr/linker/common-rom/common-rom-misc.ld index 1559d1463ebd6..ebc239f96968e 100644 --- a/include/zephyr/linker/common-rom/common-rom-misc.ld +++ b/include/zephyr/linker/common-rom/common-rom-misc.ld @@ -22,6 +22,10 @@ ITERABLE_SECTION_ROM(sensor_decoder_api, Z_LINK_ITERABLE_SUBALIGN) #endif +#if defined(CONFIG_ADC_STREAM) + ITERABLE_SECTION_ROM(adc_decoder_api, Z_LINK_ITERABLE_SUBALIGN) +#endif + #if defined(CONFIG_MCUMGR) ITERABLE_SECTION_ROM(mcumgr_handler, Z_LINK_ITERABLE_SUBALIGN) #endif diff --git a/samples/drivers/adc/adc_stream/CMakeLists.txt b/samples/drivers/adc/adc_stream/CMakeLists.txt new file mode 100644 index 0000000000000..ebbcd1526db74 --- /dev/null +++ b/samples/drivers/adc/adc_stream/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(adc_stream) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/drivers/adc/adc_stream/README.rst b/samples/drivers/adc/adc_stream/README.rst new file mode 100644 index 0000000000000..a00570ea2a7ae --- /dev/null +++ b/samples/drivers/adc/adc_stream/README.rst @@ -0,0 +1,42 @@ +.. zephyr:code-sample:: adc_stream + :name: Generic ADC stream + :relevant-api: adc_interface + + Get data from a ADC using stream. + +Overview +******** + +This sample application demonstrates how to use ADC stream APIs. + +Building and Running +******************** + +This sample supports one ADC. ADC needs to be aliased as ``adc0`` in devicetree. +For example: + +.. code-block:: devicetree + + / { + aliases { + adc0 = &ad4052; + }; + }; + +Make sure the aliase are in devicetree, then build and run with: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/adc/adc_stream + :board: + :goals: build flash + :compact: + +Sample Output +============= + +.. code-block:: console + + ADC data for adc405@0 (0.000074) 942995000ns + ADC data for adc405@0 (0.000446) 963059000ns + ADC data for adc405@0 (0.000297) 983124000ns + ADC data for adc405@0 (0.000446) 1003189000ns diff --git a/samples/drivers/adc/adc_stream/ad4052-stream.conf b/samples/drivers/adc/adc_stream/ad4052-stream.conf new file mode 100644 index 0000000000000..27d4b451f7ba9 --- /dev/null +++ b/samples/drivers/adc/adc_stream/ad4052-stream.conf @@ -0,0 +1,7 @@ +# Copyright (c) 2025 Analog Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 +CONFIG_SPI=y +CONFIG_SPI_RTIO=y +CONFIG_AD405X_STREAM=y +CONFIG_COUNTER=y +CONFIG_COUNTER_TIMER_MAX32=y diff --git a/samples/drivers/adc/adc_stream/boards/apard32690_max32690_m4_ad4052.overlay b/samples/drivers/adc/adc_stream/boards/apard32690_max32690_m4_ad4052.overlay new file mode 100644 index 0000000000000..d408d245757e9 --- /dev/null +++ b/samples/drivers/adc/adc_stream/boards/apard32690_max32690_m4_ad4052.overlay @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2025 Analog Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&arduino_spi { + adc4052_eval_ad4052_ardz: adc4052@0 { + sampling-period = <20000>; /*uS*/ + }; +}; diff --git a/samples/drivers/adc/adc_stream/boards/apard32690_max32690_m4_max32.overlay b/samples/drivers/adc/adc_stream/boards/apard32690_max32690_m4_max32.overlay new file mode 100644 index 0000000000000..2f023fa7893f6 --- /dev/null +++ b/samples/drivers/adc/adc_stream/boards/apard32690_max32690_m4_max32.overlay @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 Analog Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc; + }; + + zephyr,user { + io-channels = <&adc 0>; + }; +}; + +&adc_clk_ext_p0_9 { + power-source = ; +}; + +&adc { + reg = <0x40034000 0x1000>; + pinctrl-0 = <&adc_clk_ext_p0_9>; + pinctrl-names = "default"; + + /* ADC parameters set up 25KSPS sampling rate */ + track-count = <96>; + idle-count = <183>; + + #address-cells = <1>; + #size-cells = <0>; + #io-channel-cells = <1>; + status = "okay"; + compatible = "adi,max32-adc"; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + zephyr,vref-mv = <1250>; + }; +}; diff --git a/samples/drivers/adc/adc_stream/max32-stream.conf b/samples/drivers/adc/adc_stream/max32-stream.conf new file mode 100644 index 0000000000000..2fe955b1aa11e --- /dev/null +++ b/samples/drivers/adc/adc_stream/max32-stream.conf @@ -0,0 +1,3 @@ +# Copyright (c) 2025 Analog Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 +CONFIG_ADC_MAX32_STREAM=y diff --git a/samples/drivers/adc/adc_stream/prj.conf b/samples/drivers/adc/adc_stream/prj.conf new file mode 100644 index 0000000000000..504d2f5e81f76 --- /dev/null +++ b/samples/drivers/adc/adc_stream/prj.conf @@ -0,0 +1,3 @@ +CONFIG_ADC=y +CONFIG_GPIO=y +CONFIG_ADC_STREAM=y diff --git a/samples/drivers/adc/adc_stream/sample.yaml b/samples/drivers/adc/adc_stream/sample.yaml new file mode 100644 index 0000000000000..5283f9b6d828f --- /dev/null +++ b/samples/drivers/adc/adc_stream/sample.yaml @@ -0,0 +1,25 @@ +sample: + name: ADC stream sample +common: + tags: adc + harness: console + harness_config: + type: one_line + regex: + - "^ADC data for [^@]+@\\d+ \\([0-9.]+\\) \\d+n$" +tests: + sample.driver.adc_stream: + filter: dt_alias_exists("adc0") + sample.driver.adc_stream.ad4052-stream: + extra_args: + - SHIELD=eval_ad4052_ardz + - EXTRA_CONF_FILE=ad4052-stream.conf + - DTC_OVERLAY_FILE=boards/apard32690_max32690_m4_ad4052.overlay + platform_allow: + - apard32690/max32690/m4 + sample.driver.adc_stream.max32-stream: + extra_args: + - EXTRA_CONF_FILE=max32-stream.conf + - DTC_OVERLAY_FILE=boards/apard32690_max32690_m4_max32.overlay + platform_allow: + - apard32690/max32690/m4 diff --git a/samples/drivers/adc/adc_stream/src/main.c b/samples/drivers/adc/adc_stream/src/main.c new file mode 100644 index 0000000000000..a24866581819c --- /dev/null +++ b/samples/drivers/adc/adc_stream/src/main.c @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * Copyright (c) 2025 Analog Devices, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +/* ADC node from the devicetree. */ +#define SAMPLE_ADC_NODE DT_ALIAS(adc0) + +#define SAMPLE_DT_SPEC_AND_COMMA(node_id, prop, idx) ADC_DT_SPEC_GET_BY_IDX(node_id, idx), + +#if DT_NODE_HAS_PROP(DT_PATH(zephyr_user), io_channels) +/* Data of ADC io-channels specified in devicetree. */ +static const struct adc_dt_spec adc_channels[] = { + DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, SAMPLE_DT_SPEC_AND_COMMA) +}; +static const int adc_channels_count = ARRAY_SIZE(adc_channels); +#endif + +static void init_adc(void) +{ + int i, ret; + + ret = adc_is_ready_dt(&adc_channels[0]); + + for (i = 0; i < adc_channels_count; i++) { + ret = adc_channel_setup_dt(&adc_channels[i]); + } +} + +/* Define triggers for ADC stream. Trigger stands for an event that + * will cause the ADC to read data and perform an operation on it. + * + * Format for each trigger is: + * {trigger, operation to be done on the data associated to the trigger} + * + * For more information about supported triggers and data operations, + * refer to the enums adc_trigger_type and adc_stream_data_opt. + */ +#define SAMPLE_ADC_TRIGGERS \ + {ADC_TRIG_FIFO_FULL, ADC_STREAM_DATA_INCLUDE}, \ + {ADC_TRIG_FIFO_WATERMARK, ADC_STREAM_DATA_INCLUDE} + +ADC_DT_STREAM_IODEV(iodev, SAMPLE_ADC_NODE, adc_channels, SAMPLE_ADC_TRIGGERS); + +/* Mempool is used for sharing ADC data that has been read between ADC driver and the application. + * Data read in the ADC driver is stored in the mempool and can be processed in the application. + * Current values are set to support range of ADC drivers and can be optimized for smaller memory + * footprint. sizeof(void *) is used so memory blocks are properly aligned for different + * platforms. + */ +RTIO_DEFINE_WITH_MEMPOOL(adc_ctx, 16, 16, 20, 256, sizeof(void *)); + +static int print_adc_stream(const struct device *adc, struct rtio_iodev *local_iodev) +{ + int rc = 0; + const struct adc_decoder_api *decoder; + struct rtio_cqe *cqe; + uint8_t *buf; + uint32_t buf_len; + struct rtio_sqe *handles; + + /* Start the streams */ + adc_stream(local_iodev, &adc_ctx, NULL, &handles); + + while (1) { + /* CQE is a RTIO completion event. It is created in a ADC driver that is + * doing the streaming when there is a batch of data to be processed + * or some error occurred during ADC streaming. CQE contains result of + * data reading and userdata (if one is passed in adc_stream). + * It is used to retrieve mempool buffer where data is stored. + */ + cqe = rtio_cqe_consume_block(&adc_ctx); + + if (cqe->result != 0) { + printk("async read failed %d\n", cqe->result); + return cqe->result; + } + + rc = rtio_cqe_get_mempool_buffer(&adc_ctx, cqe, &buf, &buf_len); + + if (rc != 0) { + printk("get mempool buffer failed %d\n", rc); + return rc; + } + + rtio_cqe_release(&adc_ctx, cqe); + + rc = adc_get_decoder(adc, &decoder); + + if (rc != 0) { + printk("sensor_get_decoder failed %d\n", rc); + return rc; + } + + /* Frame iterator values when data comes from a FIFO */ + uint32_t adc_fit = 0; + struct adc_data adc_data = {0}; + + /* Number of accelerometer data frames */ + uint16_t frame_count; + + rc = decoder->get_frame_count(buf, 0, &frame_count); + + if (rc != 0) { + printk("get_frame_count failed %d\n", rc); + return rc; + } + + /* Decode all available accelerometer sample frames */ + for (int i = 0; i < frame_count; i++) { + decoder->decode(buf, 0, &adc_fit, 1, &adc_data); + + printk("ADC data for %s (%" PRIq(6) ") %lluns\n", adc->name, + PRIq_arg(adc_data.readings[0].value, 6, adc_data.shift), + (adc_data.header.base_timestamp_ns + + adc_data.readings[0].timestamp_delta)); + } + + rtio_release_buffer(&adc_ctx, buf, buf_len); + } + + return rc; +} + +int main(void) +{ + int ret; + struct adc_sequence sequence; + struct adc_read_config *read_cfg = iodev.data; + + read_cfg->sequence = &sequence; + + init_adc(); + ret = adc_sequence_init_dt(&adc_channels[0], &sequence); + if (ret < 0) { + printk("Failed to initialize ADC sequence: %d\n", ret); + return 0; + } + + print_adc_stream(adc_channels[0].dev, &iodev); + + return 0; +} diff --git a/scripts/build/gen_kobject_list.py b/scripts/build/gen_kobject_list.py index 1d60c0df0fc9d..85d29eec13218 100755 --- a/scripts/build/gen_kobject_list.py +++ b/scripts/build/gen_kobject_list.py @@ -114,7 +114,8 @@ ("ztest_test_rule", ("CONFIG_ZTEST", True, False)), ("rtio", ("CONFIG_RTIO", False, False)), ("rtio_iodev", ("CONFIG_RTIO", False, False)), - ("sensor_decoder_api", ("CONFIG_SENSOR_ASYNC_API", True, False)) + ("sensor_decoder_api", ("CONFIG_SENSOR_ASYNC_API", True, False)), + ("adc_decoder_api", ("CONFIG_ADC_STREAM", True, False)) ]) def kobject_to_enum(kobj): diff --git a/soc/adi/max32/Kconfig.soc b/soc/adi/max32/Kconfig.soc index e4b2347ca0218..a56c32955e462 100644 --- a/soc/adi/max32/Kconfig.soc +++ b/soc/adi/max32/Kconfig.soc @@ -69,8 +69,12 @@ config SOC_MAX32680_M4 select SOC_MAX32680 select SOC_FAMILY_MAX32_M4 +config HAS_ADC_MAX32_REVB_ME18 + bool "revb" + config SOC_MAX32690 bool + select HAS_ADC_MAX32_REVB_ME18 config SOC_MAX32690_M4 bool diff --git a/west.yml b/west.yml index fe8253bfa2f22..44451584d229e 100644 --- a/west.yml +++ b/west.yml @@ -144,7 +144,7 @@ manifest: groups: - fs - name: hal_adi - revision: 16829b77264678f31a2d077a870af7bdca2d39bd + revision: pull/29/head path: modules/hal/adi groups: - hal