diff --git a/boards/posix/native_posix/native_posix.yaml b/boards/posix/native_posix/native_posix.yaml index cff04e6d23a..dde2d0a13fc 100644 --- a/boards/posix/native_posix/native_posix.yaml +++ b/boards/posix/native_posix/native_posix.yaml @@ -9,5 +9,6 @@ supported: - eeprom - netif:eth - usb_device + - adc testing: default: true diff --git a/boards/posix/native_posix/native_posix_64.yaml b/boards/posix/native_posix/native_posix_64.yaml index d80155f78d6..ee063a9952d 100644 --- a/boards/posix/native_posix/native_posix_64.yaml +++ b/boards/posix/native_posix/native_posix_64.yaml @@ -9,3 +9,4 @@ supported: - eeprom - netif:eth - usb_device + - adc diff --git a/drivers/adc/CMakeLists.txt b/drivers/adc/CMakeLists.txt index afa04b68b73..a88ca2e35d9 100644 --- a/drivers/adc/CMakeLists.txt +++ b/drivers/adc/CMakeLists.txt @@ -19,3 +19,4 @@ zephyr_library_sources_ifdef(CONFIG_ADC_MCP320X adc_mcp320x.c) zephyr_library_sources_ifdef(CONFIG_ADC_NPCX adc_npcx.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE adc_handlers.c) zephyr_library_sources_ifdef(CONFIG_ADC_CC32XX adc_cc32xx.c) +zephyr_library_sources_ifdef(CONFIG_ADC_EMUL adc_emul.c) diff --git a/drivers/adc/Kconfig b/drivers/adc/Kconfig index 81133f5e0f5..e18be260849 100644 --- a/drivers/adc/Kconfig +++ b/drivers/adc/Kconfig @@ -60,4 +60,6 @@ source "drivers/adc/Kconfig.npcx" source "drivers/adc/Kconfig.cc32xx" +source "drivers/adc/Kconfig.adc_emul" + endif # ADC diff --git a/drivers/adc/Kconfig.adc_emul b/drivers/adc/Kconfig.adc_emul new file mode 100644 index 00000000000..8f3bc5b7765 --- /dev/null +++ b/drivers/adc/Kconfig.adc_emul @@ -0,0 +1,27 @@ +# Copyright 2021 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +config ADC_EMUL + bool "ADC emulator" + help + Enable the ADC emulator driver. This is a fake driver in that it + does not talk to real hardware. It prenteds to be actual ADC. It + is used for testing higher-level API for ADC devices. + +if ADC_EMUL + +config ADC_EMUL_ACQUISITION_THREAD_STACK_SIZE + int "Stack size for the ADC data acquisition thread" + default 512 + help + Size of the stack used for the internal data acquisition + thread. Increasing size may be required when value function for + emulated ADC require a lot of memory. + +config ADC_EMUL_ACQUISITION_THREAD_PRIO + int "Priority for the ADC data acquisition thread" + default 0 + help + Priority level for the internal ADC data acquisition thread. + +endif # ADC_EMUL diff --git a/drivers/adc/adc_emul.c b/drivers/adc/adc_emul.c new file mode 100644 index 00000000000..2acf7a872a3 --- /dev/null +++ b/drivers/adc/adc_emul.c @@ -0,0 +1,580 @@ +/** + * @file + * + * @brief Emulated ADC driver + */ + +/* + * Copyright 2021 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_adc_emul + +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(adc_emul, CONFIG_ADC_LOG_LEVEL); + +#define ADC_CONTEXT_USES_KERNEL_TIMER +#include "adc_context.h" + +#define ADC_EMUL_MAX_RESOLUTION 16 + +typedef uint16_t adc_emul_res_t; + +enum adc_emul_input_source { + ADC_EMUL_CONST_VALUE, + ADC_EMUL_CUSTOM_FUNC, +}; + +/** + * @brief Channel of emulated ADC config + * + * This structure contains configuration of one channel of emualted ADC. + */ +struct adc_emul_chan_cfg { + /** Pointer to function used to obtain input mV */ + adc_emul_value_func func; + /** Pointer to data that are passed to @a func on call */ + void *func_data; + /** Constant mV input value */ + uint32_t const_value; + /** Gain used on output value */ + enum adc_gain gain; + /** Reference source */ + enum adc_reference ref; + /** Input source which is used to obtain input value */ + enum adc_emul_input_source input; +}; + +/** + * @brief Emulated ADC config + * + * This structure contains constant data for given instance of emulated ADC. + */ +struct adc_emul_config { + /** Number of supported channels */ + uint8_t num_channels; +}; + +/** + * @brief Emulated ADC data + * + * This structure contains data structures used by a emulated ADC. + */ +struct adc_emul_data { + /** Structure that handle state of ongoing read operation */ + struct adc_context ctx; + /** Pointer to ADC emulator own device structure */ + const struct device *dev; + /** Pointer to memory where next sample will be written */ + uint16_t *buf; + /** Pointer to where will be data stored in case of repeated sampling */ + uint16_t *repeat_buf; + /** Mask with channels that will be sampled */ + uint32_t channels; + /** Mask created from requested resolution in read operation */ + uint16_t res_mask; + /** Reference voltage for ADC_REF_VDD_1 source */ + uint16_t ref_vdd; + /** Reference voltage for ADC_REF_EXTERNAL0 source */ + uint16_t ref_ext0; + /** Reference voltage for ADC_REF_EXTERNAL1 source */ + uint16_t ref_ext1; + /** Reference voltage for ADC_REF_INTERNAL source */ + uint16_t ref_int; + /** Array of each channel configuration */ + struct adc_emul_chan_cfg *chan_cfg; + /** Structure used for acquisition thread */ + struct k_thread thread; + /** Semaphore used to control acquisiton thread */ + struct k_sem sem; + /** Mutex used to control access to channels config and ref voltages */ + struct k_mutex cfg_mtx; + + /** Stack for acquisition thread */ + K_KERNEL_STACK_MEMBER(stack, + CONFIG_ADC_EMUL_ACQUISITION_THREAD_STACK_SIZE); +}; + +int adc_emul_const_value_set(const struct device *dev, unsigned int chan, + uint32_t value) +{ + const struct adc_emul_config *config = dev->config; + struct adc_emul_data *data = dev->data; + struct adc_emul_chan_cfg *chan_cfg; + + if (chan >= config->num_channels) { + LOG_ERR("unsupported channel %d", chan); + return -EINVAL; + } + + chan_cfg = &data->chan_cfg[chan]; + + k_mutex_lock(&data->cfg_mtx, K_FOREVER); + + chan_cfg->input = ADC_EMUL_CONST_VALUE; + chan_cfg->const_value = value; + + k_mutex_unlock(&data->cfg_mtx); + + return 0; +} + +int adc_emul_value_func_set(const struct device *dev, unsigned int chan, + adc_emul_value_func func, void *func_data) +{ + const struct adc_emul_config *config = dev->config; + struct adc_emul_data *data = dev->data; + struct adc_emul_chan_cfg *chan_cfg; + + if (chan >= config->num_channels) { + LOG_ERR("unsupported channel %d", chan); + return -EINVAL; + } + + chan_cfg = &data->chan_cfg[chan]; + + k_mutex_lock(&data->cfg_mtx, K_FOREVER); + + chan_cfg->func = func; + chan_cfg->func_data = func_data; + chan_cfg->input = ADC_EMUL_CUSTOM_FUNC; + + k_mutex_unlock(&data->cfg_mtx); + + return 0; +} + +int adc_emul_ref_voltage_set(const struct device *dev, enum adc_reference ref, + uint16_t value) +{ + struct adc_driver_api *api = (struct adc_driver_api *)dev->api; + struct adc_emul_data *data = dev->data; + int err = 0; + + k_mutex_lock(&data->cfg_mtx, K_FOREVER); + + switch (ref) { + case ADC_REF_VDD_1: + data->ref_vdd = value; + break; + case ADC_REF_INTERNAL: + data->ref_int = value; + api->ref_internal = value; + break; + case ADC_REF_EXTERNAL0: + data->ref_ext0 = value; + break; + case ADC_REF_EXTERNAL1: + data->ref_ext1 = value; + break; + default: + err = -EINVAL; + } + + k_mutex_unlock(&data->cfg_mtx); + + return err; +} + +/** + * @brief Convert @p ref to reference voltage value in mV + * + * @param data Internal data of ADC emulator + * @param ref Select which reference source should be used + * + * @return Reference voltage in mV + * @return 0 on error + */ +static uint16_t adc_emul_get_ref_voltage(struct adc_emul_data *data, + enum adc_reference ref) +{ + uint16_t voltage; + + k_mutex_lock(&data->cfg_mtx, K_FOREVER); + + switch (ref) { + case ADC_REF_VDD_1: + voltage = data->ref_vdd; + break; + case ADC_REF_VDD_1_2: + voltage = data->ref_vdd / 2; + break; + case ADC_REF_VDD_1_3: + voltage = data->ref_vdd / 3; + break; + case ADC_REF_VDD_1_4: + voltage = data->ref_vdd / 4; + break; + case ADC_REF_INTERNAL: + voltage = data->ref_int; + break; + case ADC_REF_EXTERNAL0: + voltage = data->ref_ext0; + break; + case ADC_REF_EXTERNAL1: + voltage = data->ref_ext1; + break; + default: + voltage = 0; + } + + k_mutex_unlock(&data->cfg_mtx); + + return voltage; +} + +static int adc_emul_channel_setup(const struct device *dev, + const struct adc_channel_cfg *channel_cfg) +{ + const struct adc_emul_config *config = dev->config; + struct adc_emul_chan_cfg *emul_chan_cfg; + struct adc_emul_data *data = dev->data; + + if (channel_cfg->channel_id >= config->num_channels) { + LOG_ERR("unsupported channel id '%d'", channel_cfg->channel_id); + return -ENOTSUP; + } + + if (adc_emul_get_ref_voltage(data, channel_cfg->reference) == 0) { + LOG_ERR("unsupported channel reference '%d'", + channel_cfg->reference); + return -ENOTSUP; + } + + if (channel_cfg->differential) { + LOG_ERR("unsupported differential mode"); + return -ENOTSUP; + } + + emul_chan_cfg = &data->chan_cfg[channel_cfg->channel_id]; + + k_mutex_lock(&data->cfg_mtx, K_FOREVER); + + emul_chan_cfg->gain = channel_cfg->gain; + emul_chan_cfg->ref = channel_cfg->reference; + + k_mutex_unlock(&data->cfg_mtx); + + return 0; +} + +/** + * @brief Check if buffer in @p sequence is big enough to hold all ADC samples + * + * @param dev ADC emulator device + * @param sequence ADC sequence description + * + * @return 0 on success + * @return -ENOMEM if buffer is not big enough + */ +static int adc_emul_check_buffer_size(const struct device *dev, + const struct adc_sequence *sequence) +{ + const struct adc_emul_config *config = dev->config; + uint8_t channels = 0; + size_t needed; + uint32_t mask; + + for (mask = BIT(config->num_channels - 1); mask != 0; mask >>= 1) { + if (mask & sequence->channels) { + channels++; + } + } + + needed = channels * sizeof(adc_emul_res_t); + if (sequence->options) { + needed *= (1 + sequence->options->extra_samplings); + } + + if (sequence->buffer_size < needed) { + return -ENOMEM; + } + + return 0; +} + +/** + * @brief Start processing read request + * + * @param dev ADC emulator device + * @param sequence ADC sequence description + * + * @return 0 on success + * @return -ENOTSUP if requested resolution or channel is out side of supported + * range + * @return -ENOMEM if buffer is not big enough + * (see @ref adc_emul_check_buffer_size) + * @return other error code returned by adc_context_wait_for_completion + */ +static int adc_emul_start_read(const struct device *dev, + const struct adc_sequence *sequence) +{ + const struct adc_emul_config *config = dev->config; + struct adc_emul_data *data = dev->data; + int err; + + if (sequence->resolution > ADC_EMUL_MAX_RESOLUTION || + sequence->resolution == 0) { + LOG_ERR("unsupported resolution %d", sequence->resolution); + return -ENOTSUP; + } + + if (find_msb_set(sequence->channels) > config->num_channels) { + LOG_ERR("unsupported channels in mask: 0x%08x", + sequence->channels); + return -ENOTSUP; + } + + err = adc_emul_check_buffer_size(dev, sequence); + if (err) { + LOG_ERR("buffer size too small"); + return err; + } + + data->res_mask = BIT_MASK(sequence->resolution); + data->buf = sequence->buffer; + adc_context_start_read(&data->ctx, sequence); + + return adc_context_wait_for_completion(&data->ctx); +} + +static int adc_emul_read_async(const struct device *dev, + const struct adc_sequence *sequence, + struct k_poll_signal *async) +{ + struct adc_emul_data *data = dev->data; + int err; + + adc_context_lock(&data->ctx, async ? true : false, async); + err = adc_emul_start_read(dev, sequence); + adc_context_release(&data->ctx, err); + + return err; +} + +static int adc_emul_read(const struct device *dev, + const struct adc_sequence *sequence) +{ + return adc_emul_read_async(dev, sequence, NULL); +} + +static void adc_context_start_sampling(struct adc_context *ctx) +{ + struct adc_emul_data *data = CONTAINER_OF(ctx, struct adc_emul_data, + ctx); + + data->channels = ctx->sequence.channels; + data->repeat_buf = data->buf; + + k_sem_give(&data->sem); +} + +static void adc_context_update_buffer_pointer(struct adc_context *ctx, + bool repeat_sampling) +{ + struct adc_emul_data *data = CONTAINER_OF(ctx, struct adc_emul_data, + ctx); + + if (repeat_sampling) { + data->buf = data->repeat_buf; + } +} + +/** + * @brief Convert input voltage of ADC @p chan to raw output value + * + * @param data Internal data of ADC emulator + * @param chan ADC channel to sample + * @param result Raw output value + * + * @return 0 on success + * @return -EINVAL if failed to get reference voltage or unknown input is + * selected + * @return other error code returned by custom function + */ +static int adc_emul_get_chan_value(struct adc_emul_data *data, + unsigned int chan, + adc_emul_res_t *result) +{ + struct adc_emul_chan_cfg *chan_cfg = &data->chan_cfg[chan]; + uint32_t input_mV; + uint32_t ref_v; + uint64_t temp; /* Temporary 64 bit value prevent overflows */ + int err = 0; + + k_mutex_lock(&data->cfg_mtx, K_FOREVER); + + /* Get input voltage */ + switch (chan_cfg->input) { + case ADC_EMUL_CONST_VALUE: + input_mV = chan_cfg->const_value; + break; + + case ADC_EMUL_CUSTOM_FUNC: + err = chan_cfg->func(data->dev, chan, chan_cfg->func_data, + &input_mV); + if (err) { + LOG_ERR("failed to read channel %d (err %d)", + chan, err); + goto out; + } + break; + + default: + LOG_ERR("unknown input source %d", chan_cfg->input); + err = -EINVAL; + goto out; + } + + /* Get reference voltage and apply inverted gain */ + ref_v = adc_emul_get_ref_voltage(data, chan_cfg->ref); + err = adc_gain_invert(chan_cfg->gain, &ref_v); + if (ref_v == 0 || err) { + LOG_ERR("failed to get ref voltage (channel %d)", chan); + err = -EINVAL; + goto out; + } + + /* Calculate output value */ + temp = (uint64_t)input_mV * data->res_mask / ref_v; + + /* If output value is greater than resolution, it has to be trimmed */ + if (temp > data->res_mask) { + temp = data->res_mask; + } + + *result = temp; + +out: + k_mutex_unlock(&data->cfg_mtx); + + return err; +} + +/** + * @brief Main function of thread which is used to collect samples from + * emulated ADC. When adc_context_start_sampling give semaphore, + * for each requested channel value function is called. Returned + * mV value is converted to output using reference voltage, gain + * and requested resolution. + * + * @param data Internal data of ADC emulator + * + * @return This thread should not end + */ +static void adc_emul_acquisition_thread(struct adc_emul_data *data) +{ + int err; + + while (true) { + k_sem_take(&data->sem, K_FOREVER); + + err = 0; + + while (data->channels) { + adc_emul_res_t result = 0; + unsigned int chan = find_lsb_set(data->channels) - 1; + + LOG_DBG("reading channel %d", chan); + + err = adc_emul_get_chan_value(data, chan, &result); + if (err) { + adc_context_complete(&data->ctx, err); + break; + } + + LOG_DBG("read channel %d, result = %d", chan, result); + + *data->buf++ = result; + WRITE_BIT(data->channels, chan, 0); + } + + if (!err) { + adc_context_on_sampling_done(&data->ctx, data->dev); + } + } +} + +/** + * @brief Function called on init for each ADC emulator device. It setups all + * channels to return constant 0 mV and create acquisition thread. + * + * @param dev ADC emulator device + * + * @return 0 on success + */ +static int adc_emul_init(const struct device *dev) +{ + const struct adc_emul_config *config = dev->config; + struct adc_emul_data *data = dev->data; + int chan; + + data->dev = dev; + + k_sem_init(&data->sem, 0, 1); + k_mutex_init(&data->cfg_mtx); + + for (chan = 0; chan < config->num_channels; chan++) { + struct adc_emul_chan_cfg *chan_cfg = &data->chan_cfg[chan]; + + chan_cfg->func = NULL; + chan_cfg->func_data = NULL; + chan_cfg->input = ADC_EMUL_CONST_VALUE; + chan_cfg->const_value = 0; + } + + k_thread_create(&data->thread, data->stack, + CONFIG_ADC_EMUL_ACQUISITION_THREAD_STACK_SIZE, + (k_thread_entry_t)adc_emul_acquisition_thread, + data, NULL, NULL, + CONFIG_ADC_EMUL_ACQUISITION_THREAD_PRIO, + 0, K_NO_WAIT); + + adc_context_unlock_unconditionally(&data->ctx); + + return 0; +} + +#define ADC_EMUL_INIT(_num) \ + static struct adc_driver_api adc_emul_api_##_num = { \ + .channel_setup = adc_emul_channel_setup, \ + .read = adc_emul_read, \ + .ref_internal = DT_INST_PROP(_num, ref_internal_mv), \ + IF_ENABLED(CONFIG_ADC_ASYNC, \ + (.read_async = adc_emul_read_async,)) \ + }; \ + \ + static struct adc_emul_chan_cfg \ + adc_emul_ch_cfg_##_num[DT_INST_PROP(_num, nchannels)]; \ + \ + static const struct adc_emul_config adc_emul_config_##_num = { \ + .num_channels = DT_INST_PROP(_num, nchannels), \ + }; \ + \ + static struct adc_emul_data adc_emul_data_##_num = { \ + ADC_CONTEXT_INIT_TIMER(adc_emul_data_##_num, ctx), \ + ADC_CONTEXT_INIT_LOCK(adc_emul_data_##_num, ctx), \ + ADC_CONTEXT_INIT_SYNC(adc_emul_data_##_num, ctx), \ + .chan_cfg = adc_emul_ch_cfg_##_num, \ + .ref_vdd = DT_INST_PROP(_num, ref_vdd_mv), \ + .ref_ext0 = DT_INST_PROP(_num, ref_external0_mv), \ + .ref_ext1 = DT_INST_PROP(_num, ref_external1_mv), \ + .ref_int = DT_INST_PROP(_num, ref_internal_mv), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(_num, adc_emul_init, NULL, \ + &adc_emul_data_##_num, \ + &adc_emul_config_##_num, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ + &adc_emul_api_##_num) + +DT_INST_FOREACH_STATUS_OKAY(ADC_EMUL_INIT); diff --git a/drivers/adc/adc_shell.c b/drivers/adc/adc_shell.c index 346748192af..5ba561237c6 100644 --- a/drivers/adc/adc_shell.c +++ b/drivers/adc/adc_shell.c @@ -33,6 +33,8 @@ #define DT_DRV_COMPAT nuvoton_npcx_adc #elif DT_HAS_COMPAT_STATUS_OKAY(ti_cc32xx_adc) #define DT_DRV_COMPAT ti_cc32xx_adc +#elif DT_HAS_COMPAT_STATUS_OKAY(zephyr_adc_emul) +#define DT_DRV_COMPAT zephyr_adc_emul #else #error No known devicetree compatible match for ADC shell #endif diff --git a/dts/bindings/adc/zephyr,adc-emul.yaml b/dts/bindings/adc/zephyr,adc-emul.yaml new file mode 100644 index 00000000000..9bc956af03d --- /dev/null +++ b/dts/bindings/adc/zephyr,adc-emul.yaml @@ -0,0 +1,46 @@ +# Copyright 2021 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +description: Zephyr ADC Emulator + +compatible: "zephyr,adc-emul" + +include: adc-controller.yaml + +properties: + nchannels: + type: int + required: true + description: Number of emulated ADC channels. Should be in 1-32 range. + + ref-internal-mv: + type: int + required: false + default: 0 + description: + Internal reference voltage in mV. If not provided or set to zero, + channel setup with ADC_REF_INTERNAL will fail. + + ref-vdd-mv: + type: int + required: false + default: 0 + description: + VDD reference voltage in mV. If not provided or set to zero, + channel setup with ADC_REF_VDD_X will fail. + + ref-external0-mv: + type: int + required: false + default: 0 + description: + External 0 reference voltage in mV. If not provided or set to zero, + channel setup with ADC_REF_EXTERNAL0 will fail. + + ref-external1-mv: + type: int + required: false + default: 0 + description: + External 1 reference voltage in mV. If not provided or set to zero, + channel setup with ADC_REF_EXTERNAL1 will fail. diff --git a/include/drivers/adc/adc_emul.h b/include/drivers/adc/adc_emul.h new file mode 100644 index 00000000000..f4b90a55b6c --- /dev/null +++ b/include/drivers/adc/adc_emul.h @@ -0,0 +1,110 @@ +/** + * @file + * + * @brief Backend API for emulated ADC + */ + +/* + * Copyright 2021 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef ZEPHYR_INCLUDE_DRIVERS_ADC_ADC_EMUL_H_ +#define ZEPHYR_INCLUDE_DRIVERS_ADC_ADC_EMUL_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Emulated ADC backend API + * @defgroup adc_emul Emulated ADC + * @ingroup adc_interface + * @{ + * + * Behaviour of emulated ADC is application-defined. As-such, each + * application may + * + * - define a Device Tree overlay file to indicate the number of ADC + * controllers as well as the number of channels for each controller + * - set default reference voltages in Device Tree or using + * @ref adc_emul_ref_voltage_set + * - asynchronously call @ref adc_emul_const_value_set in order to set + * constant mV value on emulated ADC input + * - asynchronously call @ref adc_emul_value_func_set in order to assign + * function which will be used to obtain voltage on emulated ADC input + * + * An example of an appropriate Device Tree overlay file is in + * tests/drivers/adc/adc_api/boards/native_posix.overlay + * + * An example of using emulated ADC backend API is in the file + * tests/drivers/adc/adc_emul/src/main.c + */ + +/** + * @brief Type definition of the function which is used to obtain ADC + * mV input values + * + * @param dev Pointer to the device structure for the driver instance + * @param chan ADC channel to sample + * @param data User data which was passed on @ref adc_emul_value_func_set + * @param result The result value which will be set as input for ADC @p chan + * + * @return 0 on success + * @return other as error code which ends ADC context + */ +typedef int (*adc_emul_value_func)(const struct device *dev, unsigned int chan, + void *data, uint32_t *result); + +/** + * @brief Set constant mV value input for emulated ADC @p chan + * + * @param dev The emulated ADC device + * @param chan The channel of ADC which input is assigned + * @param value New voltage in mV to assign to @p chan input + * + * @return 0 on success + * @return -EINVAL if an invalid argument is provided + */ +int adc_emul_const_value_set(const struct device *dev, unsigned int chan, + uint32_t value); + +/** + * @brief Set function used to obtain voltage for input of emulated + * ADC @p chan + * + * @param dev The emulated ADC device + * @param chan The channel of ADC to which @p func is assigned + * @param func New function to assign to @p chan + * @param data Pointer to data passed to @p func on call + * + * @return 0 on success + * @return -EINVAL if an invalid argument is provided + */ +int adc_emul_value_func_set(const struct device *dev, unsigned int chan, + adc_emul_value_func func, void *data); + +/** + * @brief Set reference voltage + * + * @param dev The emulated ADC device + * @param ref Reference config which is changed + * @param value New reference voltage in mV + * + * @return 0 on success + * @return -EINVAL if an invalid argument is provided + */ +int adc_emul_ref_voltage_set(const struct device *dev, enum adc_reference ref, + uint16_t value); +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_ADC_ADC_EMUL_H_ */ diff --git a/tests/drivers/adc/adc_api/boards/native_posix.conf b/tests/drivers/adc/adc_api/boards/native_posix.conf new file mode 100644 index 00000000000..74b7d94956b --- /dev/null +++ b/tests/drivers/adc/adc_api/boards/native_posix.conf @@ -0,0 +1,4 @@ +# Copyright 2021 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_ADC_EMUL=y diff --git a/tests/drivers/adc/adc_api/boards/native_posix.overlay b/tests/drivers/adc/adc_api/boards/native_posix.overlay new file mode 100644 index 00000000000..aa5c30a03d0 --- /dev/null +++ b/tests/drivers/adc/adc_api/boards/native_posix.overlay @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + adc0: adc { + compatible = "zephyr,adc-emul"; + nchannels = <2>; + ref-internal-mv = <3300>; + ref-external1-mv = <5000>; + #io-channel-cells = <1>; + label = "ADC_0"; + status = "okay"; + }; +}; diff --git a/tests/drivers/adc/adc_api/boards/native_posix_64.conf b/tests/drivers/adc/adc_api/boards/native_posix_64.conf new file mode 100644 index 00000000000..74b7d94956b --- /dev/null +++ b/tests/drivers/adc/adc_api/boards/native_posix_64.conf @@ -0,0 +1,4 @@ +# Copyright 2021 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_ADC_EMUL=y diff --git a/tests/drivers/adc/adc_api/boards/native_posix_64.overlay b/tests/drivers/adc/adc_api/boards/native_posix_64.overlay new file mode 100644 index 00000000000..705d229e000 --- /dev/null +++ b/tests/drivers/adc/adc_api/boards/native_posix_64.overlay @@ -0,0 +1,6 @@ +/* + * Copyright 2021 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "native_posix.overlay" diff --git a/tests/drivers/adc/adc_api/src/test_adc.c b/tests/drivers/adc/adc_api/src/test_adc.c index c9476f695b6..c1b293d0527 100644 --- a/tests/drivers/adc/adc_api/src/test_adc.c +++ b/tests/drivers/adc/adc_api/src/test_adc.c @@ -289,6 +289,15 @@ #define ADC_1ST_CHANNEL_ID 0 #define ADC_2ND_CHANNEL_ID 1 +#elif defined(CONFIG_BOARD_NATIVE_POSIX) +#define ADC_DEVICE_NAME DT_LABEL(DT_INST(0, zephyr_adc_emul)) +#define ADC_RESOLUTION 10 +#define ADC_GAIN ADC_GAIN_1 +#define ADC_REFERENCE ADC_REF_INTERNAL +#define ADC_ACQUISITION_TIME ADC_ACQ_TIME_DEFAULT +#define ADC_1ST_CHANNEL_ID 0 +#define ADC_2ND_CHANNEL_ID 1 + #else #error "Unsupported board." #endif diff --git a/tests/drivers/adc/adc_emul/CMakeLists.txt b/tests/drivers/adc/adc_emul/CMakeLists.txt new file mode 100644 index 00000000000..975b77d8fe1 --- /dev/null +++ b/tests/drivers/adc/adc_emul/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright 2021 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(adc_emul) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/drivers/adc/adc_emul/app.overlay b/tests/drivers/adc/adc_emul/app.overlay new file mode 100644 index 00000000000..7e48091308b --- /dev/null +++ b/tests/drivers/adc/adc_emul/app.overlay @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + adc_emulator_test_dev { + compatible = "zephyr,adc-emul"; + nchannels = <2>; + ref-internal-mv = <3300>; + ref-external1-mv = <5000>; + #io-channel-cells = <1>; + label = "ADC_0"; + status = "okay"; + }; +}; diff --git a/tests/drivers/adc/adc_emul/prj.conf b/tests/drivers/adc/adc_emul/prj.conf new file mode 100644 index 00000000000..947887a08ca --- /dev/null +++ b/tests/drivers/adc/adc_emul/prj.conf @@ -0,0 +1,7 @@ +CONFIG_ZTEST=y + +CONFIG_ADC=y +CONFIG_ADC_LOG_LEVEL_INF=y +CONFIG_HEAP_MEM_POOL_SIZE=1024 +CONFIG_TEST_USERSPACE=y +CONFIG_ADC_EMUL=y diff --git a/tests/drivers/adc/adc_emul/src/main.c b/tests/drivers/adc/adc_emul/src/main.c new file mode 100644 index 00000000000..96d2b66441f --- /dev/null +++ b/tests/drivers/adc/adc_emul/src/main.c @@ -0,0 +1,656 @@ +/* + * Copyright 2021 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define ADC_DEVICE_NAME DT_LABEL(DT_INST(0, zephyr_adc_emul)) +#define ADC_REF_INTERNAL_MV DT_PROP(DT_INST(0, zephyr_adc_emul), ref_internal_mv) +#define ADC_REF_EXTERNAL1_MV DT_PROP(DT_INST(0, zephyr_adc_emul), ref_external1_mv) +#define ADC_RESOLUTION 14 +#define ADC_ACQUISITION_TIME ADC_ACQ_TIME_DEFAULT +#define ADC_1ST_CHANNEL_ID 0 +#define ADC_2ND_CHANNEL_ID 1 + +#define INVALID_ADC_VALUE SHRT_MIN +/* Raw to milivolt conversion doesn't handle rounding */ +#define MV_OUTPUT_EPS 2 +#define SEQUENCE_STEP 100 + +#define BUFFER_SIZE 6 +static ZTEST_BMEM int16_t m_sample_buffer[BUFFER_SIZE]; + +/** + * @brief Get ADC emulated device + * + * @return pointer to ADC device + */ +const struct device *get_adc_device(void) +{ + const struct device *adc_dev = device_get_binding(ADC_DEVICE_NAME); + + zassert_not_null(adc_dev, "Cannot get ADC device"); + + return adc_dev; +} + +/** + * @brief Setup channel with specific reference and gain + * + * @param adc_dev Pointer to ADC device + * @param ref ADC reference voltage source + * @param gain Gain applied to ADC @p channel + * @param channel ADC channel which is being setup + * + * @return none + */ +static void channel_setup(const struct device *adc_dev, enum adc_reference ref, + enum adc_gain gain, int channel) +{ + int ret; + struct adc_channel_cfg channel_cfg = { + .gain = gain, + .reference = ref, + .acquisition_time = ADC_ACQUISITION_TIME, + .channel_id = channel, + }; + + ret = adc_channel_setup(adc_dev, &channel_cfg); + zassert_ok(ret, "Setting up of the %d channel failed with code %d", + channel, ret); +} + +/** + * @brief Check if samples for specific channel are correct. It can check + * samples with arithmetic sequence with common difference. + * + * @param expected_count Number of samples that are expected to be set + * @param start_mv_value Voltage in mV that was set on input at the beginning + * of sampling + * @param step Common difference in arithmetic sequence which describes how + * samples were generated + * @param num_channels Number of channels that were sampled + * @param channel_id ADC channel from which samples are checked + * @param ref_mv Reference voltage in mV + * @param gain Gain applied to ADC @p channel_id + * + * @return none + */ +static void check_samples(int expected_count, int32_t start_mv_value, int step, + int num_channels, int channel_id, int32_t ref_mv, + enum adc_gain gain) +{ + int32_t output, expected; + int i, ret; + + for (i = channel_id; i < expected_count; i += num_channels) { + expected = start_mv_value + i / num_channels * step; + output = m_sample_buffer[i]; + ret = adc_raw_to_millivolts(ref_mv, gain, ADC_RESOLUTION, + &output); + zassert_ok(ret, "adc_raw_to_millivolts() failed with code %d", + ret); + zassert_within(expected, output, MV_OUTPUT_EPS, + "%u != %u [%u] should has set value", + expected, output, i); + } + +} + +/** + * @brief Check if any values in buffer were set after expected samples. + * + * @param expected_count Number of samples that are expected to be set + * + * @return none + */ +static void check_empty_samples(int expected_count) +{ + int i; + + for (i = expected_count; i < BUFFER_SIZE; i++) + zassert_equal(INVALID_ADC_VALUE, m_sample_buffer[i], + "[%u] should be empty", i); +} + +/** + * @brief Run adc_read for given channels and collect specified number of + * samples. + * + * @param adc_dev Pointer to ADC device + * @param channel_mask Mask of channels that will be sampled + * @param samples Number of requested samples for each channel + * + * @return none + */ +static void start_adc_read(const struct device *adc_dev, uint32_t channel_mask, + int samples) +{ + int ret; + const struct adc_sequence_options *options_ptr; + + const struct adc_sequence_options options = { + .extra_samplings = samples - 1, + }; + + if (samples > 1) { + options_ptr = &options; + } else { + options_ptr = NULL; + } + + const struct adc_sequence sequence = { + .options = options_ptr, + .channels = channel_mask, + .buffer = m_sample_buffer, + .buffer_size = sizeof(m_sample_buffer), + .resolution = ADC_RESOLUTION, + }; + + ret = adc_read(adc_dev, &sequence); + zassert_ok(ret, "adc_read() failed with code %d", ret); +} + +/** @brief Data for handle_seq function */ +struct handle_seq_params { + /** Current input value in mV */ + unsigned int value; +}; + +/** + * @brief Simple custom function to set as value input function for emulated + * ADC channel. It returns arithmetic sequence with SEQUENCE_STEP + * as common difference, starting from param value. + * + * @param dev Pointer to ADC device + * @param channel ADC channel for which input value is requested + * @param data Pointer to function parameters. It has to be + * struct handle_seq_params type + * @param result Pointer where input value should be stored + * + * @return 0 on success + * @return -EINVAL when current input value equals 0 + */ +static int handle_seq(const struct device *dev, unsigned int channel, + void *data, uint32_t *result) +{ + struct handle_seq_params *param = data; + + if (param->value == 0) { + return -EINVAL; + } + + *result = param->value; + param->value += SEQUENCE_STEP; + + return 0; +} + +/** @brief Test setting one channel with constant output. */ +static void test_adc_emul_single_value(void) +{ + const uint16_t input_mv = 1500; + const int samples = 4; + int ret, i; + + for (i = 0; i < BUFFER_SIZE; ++i) { + m_sample_buffer[i] = INVALID_ADC_VALUE; + } + + /* Generic ADC setup */ + const struct device *adc_dev = get_adc_device(); + + channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, + ADC_1ST_CHANNEL_ID); + + /* ADC emulator-specific setup */ + ret = adc_emul_const_value_set(adc_dev, ADC_1ST_CHANNEL_ID, input_mv); + zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); + + /* Test sampling */ + start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID), samples); + + /* Check samples */ + check_samples(samples, input_mv, 0 /* step */, 1 /* channels */, + 0 /* first channel data */, ADC_REF_INTERNAL_MV, + ADC_GAIN_1); + + check_empty_samples(samples); +} + +/** @brief Test setting two channels with different constant output */ +static void test_adc_emul_single_value_2ch(void) +{ + const uint16_t input1_mv = 3000; + const uint16_t input2_mv = 2000; + const int samples = 3; + int ret, i; + + for (i = 0; i < BUFFER_SIZE; ++i) { + m_sample_buffer[i] = INVALID_ADC_VALUE; + } + + /* Generic ADC setup */ + const struct device *adc_dev = get_adc_device(); + + channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, + ADC_1ST_CHANNEL_ID); + channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, + ADC_2ND_CHANNEL_ID); + + /* ADC emulator-specific setup */ + ret = adc_emul_const_value_set(adc_dev, ADC_1ST_CHANNEL_ID, input1_mv); + zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); + + ret = adc_emul_const_value_set(adc_dev, ADC_2ND_CHANNEL_ID, input2_mv); + zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); + + /* Test sampling */ + start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID) | + BIT(ADC_2ND_CHANNEL_ID), samples); + + /* Check samples */ + check_samples(samples * 2, input1_mv, 0 /* step */, 2 /* channels */, + 0 /* first channel data */, ADC_REF_INTERNAL_MV, + ADC_GAIN_1); + check_samples(samples * 2, input2_mv, 0 /* step */, 2 /* channels */, + 1 /* first channel data */, ADC_REF_INTERNAL_MV, + ADC_GAIN_1); + + check_empty_samples(samples * 2); +} + +/** @brief Test setting one channel with custom function. */ +static void test_adc_emul_custom_function(void) +{ + struct handle_seq_params channel1_param; + const uint16_t input_mv = 1500; + const int samples = 4; + int ret, i; + + for (i = 0; i < BUFFER_SIZE; ++i) { + m_sample_buffer[i] = INVALID_ADC_VALUE; + } + + /* Generic ADC setup */ + const struct device *adc_dev = get_adc_device(); + + channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, + ADC_1ST_CHANNEL_ID); + + /* ADC emulator-specific setup */ + channel1_param.value = input_mv; + + ret = adc_emul_value_func_set(adc_dev, ADC_1ST_CHANNEL_ID, + handle_seq, &channel1_param); + zassert_ok(ret, "adc_emul_value_func_set() failed with code %d", ret); + + /* Test sampling */ + start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID), samples); + + /* Check samples */ + check_samples(samples, input_mv, SEQUENCE_STEP, 1 /* channels */, + 0 /* first channel data */, ADC_REF_INTERNAL_MV, + ADC_GAIN_1); + + check_empty_samples(samples); +} + +/** + * @brief Test setting two channels with custom function and different + * params. + */ +static void test_adc_emul_custom_function_2ch(void) +{ + struct handle_seq_params channel1_param; + struct handle_seq_params channel2_param; + const uint16_t input1_mv = 1500; + const uint16_t input2_mv = 1000; + const int samples = 3; + int ret, i; + + for (i = 0; i < BUFFER_SIZE; ++i) { + m_sample_buffer[i] = INVALID_ADC_VALUE; + } + + /* Generic ADC setup */ + const struct device *adc_dev = get_adc_device(); + + channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, + ADC_1ST_CHANNEL_ID); + channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, + ADC_2ND_CHANNEL_ID); + + /* ADC emulator-specific setup */ + channel1_param.value = input1_mv; + channel2_param.value = input2_mv; + + ret = adc_emul_value_func_set(adc_dev, ADC_1ST_CHANNEL_ID, + handle_seq, &channel1_param); + zassert_ok(ret, "adc_emul_value_func_set() failed with code %d", ret); + + ret = adc_emul_value_func_set(adc_dev, ADC_2ND_CHANNEL_ID, + handle_seq, &channel2_param); + zassert_ok(ret, "adc_emul_value_func_set() failed with code %d", ret); + + /* Test sampling */ + start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID) | + BIT(ADC_2ND_CHANNEL_ID), samples); + + /* Check samples */ + check_samples(samples * 2, input1_mv, SEQUENCE_STEP, + 2 /* channels */, 0 /* first channel data */, + ADC_REF_INTERNAL_MV, ADC_GAIN_1); + check_samples(samples * 2, input2_mv, SEQUENCE_STEP, + 2 /* channels */, 1 /* first channel data */, + ADC_REF_INTERNAL_MV, ADC_GAIN_1); + + check_empty_samples(samples * 2); +} + +/** + * @brief Test setting two channels, one with custom function and + * one with constant value. + */ +static void test_adc_emul_custom_function_and_value(void) +{ + struct handle_seq_params channel1_param; + const uint16_t input1_mv = 1500; + const uint16_t input2_mv = 1000; + const int samples = 3; + int ret, i; + + for (i = 0; i < BUFFER_SIZE; ++i) { + m_sample_buffer[i] = INVALID_ADC_VALUE; + } + + /* Generic ADC setup */ + const struct device *adc_dev = get_adc_device(); + + channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, + ADC_1ST_CHANNEL_ID); + channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, + ADC_2ND_CHANNEL_ID); + + /* ADC emulator-specific setup */ + channel1_param.value = input1_mv; + + ret = adc_emul_value_func_set(adc_dev, ADC_1ST_CHANNEL_ID, + handle_seq, &channel1_param); + zassert_ok(ret, "adc_emul_value_func_set() failed with code %d", ret); + + ret = adc_emul_const_value_set(adc_dev, ADC_2ND_CHANNEL_ID, input2_mv); + zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); + + /* Test sampling */ + start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID) | + BIT(ADC_2ND_CHANNEL_ID), samples); + + /* Check samples */ + check_samples(samples * 2, input1_mv, SEQUENCE_STEP, + 2 /* channels */, 0 /* first channel data */, + ADC_REF_INTERNAL_MV, ADC_GAIN_1); + check_samples(samples * 2, input2_mv, 0 /* step */, 2 /* channels */, + 1 /* first channel data */, ADC_REF_INTERNAL_MV, + ADC_GAIN_1); + + check_empty_samples(samples * 2); +} + +/** @brief Test few different settings of gain argument. */ +static void test_adc_emul_gain(void) +{ + const uint16_t input_mv = 1000; + uint32_t channel_mask; + const int samples = 3; + int ret, i; + + for (i = 0; i < BUFFER_SIZE; ++i) { + m_sample_buffer[i] = INVALID_ADC_VALUE; + } + + /* Generic ADC setup */ + const struct device *adc_dev = get_adc_device(); + + channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1_6, + ADC_1ST_CHANNEL_ID); + channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_3, + ADC_2ND_CHANNEL_ID); + + /* ADC emulator-specific setup */ + channel_mask = BIT(ADC_1ST_CHANNEL_ID) | BIT(ADC_2ND_CHANNEL_ID); + + ret = adc_emul_const_value_set(adc_dev, ADC_1ST_CHANNEL_ID, input_mv); + zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); + + ret = adc_emul_const_value_set(adc_dev, ADC_2ND_CHANNEL_ID, input_mv); + zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); + + /* Test sampling */ + start_adc_read(adc_dev, channel_mask, samples); + + /* Check samples */ + check_samples(samples * 2, input_mv, 0 /* step */, 2 /* channels */, + 0 /* first channel data */, ADC_REF_INTERNAL_MV, + ADC_GAIN_1_6); + check_samples(samples * 2, input_mv, 0 /* step */, 2 /* channels */, + 1 /* first channel data */, ADC_REF_INTERNAL_MV, + ADC_GAIN_3); + + check_empty_samples(samples * 2); + + /* Change gain and re-run test */ + channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1_4, + ADC_1ST_CHANNEL_ID); + channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_2_3, + ADC_2ND_CHANNEL_ID); + + /* Test sampling */ + start_adc_read(adc_dev, channel_mask, samples); + + /* Check samples */ + check_samples(samples * 2, input_mv, 0 /* step */, 2 /* channels */, + 0 /* first channel data */, ADC_REF_INTERNAL_MV, + ADC_GAIN_1_4); + check_samples(samples * 2, input_mv, 0 /* step */, 2 /* channels */, + 1 /* first channel data */, ADC_REF_INTERNAL_MV, + ADC_GAIN_2_3); + + check_empty_samples(samples * 2); +} + +/** + * @brief Test behaviour on input higher than reference. Return value should be + * cropped to reference value and cannot exceed resolution requested in + * adc_read(). + */ +static void test_adc_emul_input_higher_than_ref(void) +{ + const uint16_t input_mv = ADC_REF_INTERNAL_MV + 100; + const int samples = 4; + int ret, i; + + for (i = 0; i < BUFFER_SIZE; ++i) { + m_sample_buffer[i] = INVALID_ADC_VALUE; + } + + /* Generic ADC setup */ + const struct device *adc_dev = get_adc_device(); + + channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, + ADC_1ST_CHANNEL_ID); + + /* ADC emulator-specific setup */ + ret = adc_emul_const_value_set(adc_dev, ADC_1ST_CHANNEL_ID, input_mv); + zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); + + /* Test sampling */ + start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID), samples); + + /* + * Check samples - returned value should max out on reference value. + * Raw value shoudn't exceed resolution. + */ + check_samples(samples, ADC_REF_INTERNAL_MV, 0 /* step */, + 1 /* channels */, 0 /* first channel data */, + ADC_REF_INTERNAL_MV, ADC_GAIN_1); + + check_empty_samples(samples); + + for (i = 0; i < samples; i++) + zassert_equal(BIT_MASK(ADC_RESOLUTION), m_sample_buffer[i], + "[%u] raw value isn't max value", i); +} + +/** + * @brief Test different reference sources and if error is reported when + * unconfigured reference source is requested. + */ +static void test_adc_emul_reference(void) +{ + const uint16_t input1_mv = 4000; + const uint16_t input2_mv = 2000; + const int samples = 3; + int ret, i; + + for (i = 0; i < BUFFER_SIZE; ++i) { + m_sample_buffer[i] = INVALID_ADC_VALUE; + } + + /* Generic ADC setup */ + const struct device *adc_dev = get_adc_device(); + + channel_setup(adc_dev, ADC_REF_EXTERNAL1, ADC_GAIN_1, + ADC_1ST_CHANNEL_ID); + + struct adc_channel_cfg channel_cfg = { + .gain = ADC_GAIN_1, + /* Reference value not setup in DTS */ + .reference = ADC_REF_EXTERNAL0, + .acquisition_time = ADC_ACQUISITION_TIME, + .channel_id = ADC_2ND_CHANNEL_ID, + }; + + ret = adc_channel_setup(adc_dev, &channel_cfg); + zassert_not_equal(ret, 0, + "Setting up of the %d channel shuldn't succeeded", + ADC_2ND_CHANNEL_ID); + + channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, + ADC_2ND_CHANNEL_ID); + + /* ADC emulator-specific setup */ + ret = adc_emul_const_value_set(adc_dev, ADC_1ST_CHANNEL_ID, input1_mv); + zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); + + ret = adc_emul_const_value_set(adc_dev, ADC_2ND_CHANNEL_ID, input2_mv); + zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); + + /* Test sampling */ + start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID) | + BIT(ADC_2ND_CHANNEL_ID), samples); + + /* Check samples */ + check_samples(samples * 2, input1_mv, 0 /* step */, 2 /* channels */, + 0 /* first channel data */, ADC_REF_EXTERNAL1_MV, + ADC_GAIN_1); + check_samples(samples * 2, input2_mv, 0 /* step */, 2 /* channels */, + 1 /* first channel data */, ADC_REF_INTERNAL_MV, + ADC_GAIN_1); + + check_empty_samples(samples * 2); +} + +/** @brief Test setting reference value. */ +static void test_adc_emul_ref_voltage_set(void) +{ + const uint16_t input1_mv = 4000; + const uint16_t input2_mv = 2000; + const uint16_t ref1_mv = 6000; + const uint16_t ref2_mv = 9000; + const int samples = 3; + int ret, i; + + for (i = 0; i < BUFFER_SIZE; ++i) { + m_sample_buffer[i] = INVALID_ADC_VALUE; + } + + /* Generic ADC setup */ + const struct device *adc_dev = get_adc_device(); + + channel_setup(adc_dev, ADC_REF_EXTERNAL1, ADC_GAIN_1, + ADC_1ST_CHANNEL_ID); + channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, + ADC_2ND_CHANNEL_ID); + + /* ADC emulator-specific setup */ + ret = adc_emul_const_value_set(adc_dev, ADC_1ST_CHANNEL_ID, input1_mv); + zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); + + ret = adc_emul_const_value_set(adc_dev, ADC_2ND_CHANNEL_ID, input2_mv); + zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); + + /* Change reference voltage */ + ret = adc_emul_ref_voltage_set(adc_dev, ADC_REF_EXTERNAL1, ref1_mv); + zassert_ok(ret, "adc_emul_ref_voltage_set() failed with code %d", ret); + + ret = adc_emul_ref_voltage_set(adc_dev, ADC_REF_INTERNAL, ref2_mv); + zassert_ok(ret, "adc_emul_ref_voltage_set() failed with code %d", ret); + + /* Test sampling */ + start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID) | + BIT(ADC_2ND_CHANNEL_ID), samples); + + /* Check samples */ + check_samples(samples * 2, input1_mv, 0 /* step */, 2 /* channels */, + 0 /* first channel data */, ref1_mv, ADC_GAIN_1); + check_samples(samples * 2, input2_mv, 0 /* step */, 2 /* channels */, + 1 /* first channel data */, ref2_mv, ADC_GAIN_1); + + check_empty_samples(samples * 2); + + /* Set previous reference voltage value */ + ret = adc_emul_ref_voltage_set(adc_dev, ADC_REF_EXTERNAL1, + ADC_REF_EXTERNAL1_MV); + zassert_ok(ret, "adc_emul_ref_voltage_set() failed with code %d", ret); + + ret = adc_emul_ref_voltage_set(adc_dev, ADC_REF_INTERNAL, + ADC_REF_INTERNAL_MV); + zassert_ok(ret, "adc_emul_ref_voltage_set() failed with code %d", ret); + + /* Test sampling */ + start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID) | + BIT(ADC_2ND_CHANNEL_ID), samples); + + /* Check samples */ + check_samples(samples * 2, input1_mv, 0 /* step */, 2 /* channels */, + 0 /* first channel data */, ADC_REF_EXTERNAL1_MV, + ADC_GAIN_1); + check_samples(samples * 2, input2_mv, 0 /* step */, 2 /* channels */, + 1 /* first channel data */, ADC_REF_INTERNAL_MV, + ADC_GAIN_1); + + check_empty_samples(samples * 2); +} + +void test_main(void) +{ + k_object_access_grant(get_adc_device(), k_current_get()); + + ztest_test_suite(adc_basic_test, + ztest_user_unit_test(test_adc_emul_single_value), + ztest_user_unit_test(test_adc_emul_single_value_2ch), + ztest_user_unit_test(test_adc_emul_custom_function), + ztest_user_unit_test(test_adc_emul_custom_function_2ch), + ztest_user_unit_test(test_adc_emul_custom_function_and_value), + ztest_user_unit_test(test_adc_emul_gain), + ztest_user_unit_test(test_adc_emul_input_higher_than_ref), + ztest_user_unit_test(test_adc_emul_reference), + ztest_user_unit_test(test_adc_emul_ref_voltage_set)); + ztest_run_test_suite(adc_basic_test); +} diff --git a/tests/drivers/adc/adc_emul/testcase.yaml b/tests/drivers/adc/adc_emul/testcase.yaml new file mode 100644 index 00000000000..e1831776ba3 --- /dev/null +++ b/tests/drivers/adc/adc_emul/testcase.yaml @@ -0,0 +1,5 @@ +common: + tags: adc drivers userspace +tests: + drivers.adc.emul: + depends_on: adc