/* * Copyright (c) 2018 Nordic Semiconductor ASA * Copyright (c) 2017 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #ifndef ZEPHYR_DRIVERS_ADC_ADC_CONTEXT_H_ #define ZEPHYR_DRIVERS_ADC_ADC_CONTEXT_H_ #include #include #ifdef __cplusplus extern "C" { #endif struct adc_context; /* * Each driver should provide implementations of the following two functions: * - adc_context_start_sampling() that will be called when a sampling (of one * or more channels, depending on the realized sequence) is to be started * - adc_context_update_buffer_pointer() that will be called when the sample * buffer pointer should be prepared for writing of next sampling results, * the "repeat_sampling" parameter indicates if the results should be written * in the same place as before (when true) or as consecutive ones (otherwise). */ static void adc_context_start_sampling(struct adc_context *ctx); static void adc_context_update_buffer_pointer(struct adc_context *ctx, bool repeat_sampling); /* * If a given driver uses some dedicated hardware timer to trigger consecutive * samplings, it should implement also the following two functions. Otherwise, * it should define the ADC_CONTEXT_USES_KERNEL_TIMER macro to enable parts of * this module that utilize a standard kernel timer. */ static void adc_context_enable_timer(struct adc_context *ctx); static void adc_context_disable_timer(struct adc_context *ctx); struct adc_context { atomic_t sampling_requested; #ifdef ADC_CONTEXT_USES_KERNEL_TIMER struct k_timer timer; #endif /* ADC_CONTEXT_USES_KERNEL_TIMER */ struct k_sem lock; struct k_sem sync; int status; #ifdef CONFIG_ADC_ASYNC struct k_poll_signal *signal; bool asynchronous; #endif /* CONFIG_ADC_ASYNC */ struct adc_sequence sequence; struct adc_sequence_options options; u16_t sampling_index; }; #ifdef ADC_CONTEXT_USES_KERNEL_TIMER #define ADC_CONTEXT_INIT_TIMER(_data, _ctx_name) \ ._ctx_name.timer = Z_TIMER_INITIALIZER(_data._ctx_name.timer, \ adc_context_on_timer_expired, \ NULL) #endif /* ADC_CONTEXT_USES_KERNEL_TIMER */ #define ADC_CONTEXT_INIT_LOCK(_data, _ctx_name) \ ._ctx_name.lock = Z_SEM_INITIALIZER(_data._ctx_name.lock, 0, 1) #define ADC_CONTEXT_INIT_SYNC(_data, _ctx_name) \ ._ctx_name.sync = Z_SEM_INITIALIZER(_data._ctx_name.sync, 0, 1) static inline void adc_context_request_next_sampling(struct adc_context *ctx) { if (atomic_inc(&ctx->sampling_requested) == 0) { adc_context_start_sampling(ctx); } else { /* * If a sampling was already requested and was not finished yet, * do not start another one from here, this will be done from * adc_context_on_sampling_done() after the current sampling is * complete. Instead, note this fact, and inform the user about * it after the sequence is done. */ ctx->status = -EBUSY; } } #ifdef ADC_CONTEXT_USES_KERNEL_TIMER static inline void adc_context_enable_timer(struct adc_context *ctx) { u32_t interval_us = ctx->options.interval_us; u32_t interval_ms = ceiling_fraction(interval_us, 1000UL); k_timer_start(&ctx->timer, K_NO_WAIT, K_MSEC(interval_ms)); } static inline void adc_context_disable_timer(struct adc_context *ctx) { k_timer_stop(&ctx->timer); } static void adc_context_on_timer_expired(struct k_timer *timer_id) { struct adc_context *ctx = CONTAINER_OF(timer_id, struct adc_context, timer); adc_context_request_next_sampling(ctx); } #endif /* ADC_CONTEXT_USES_KERNEL_TIMER */ static inline void adc_context_lock(struct adc_context *ctx, bool asynchronous, struct k_poll_signal *signal) { k_sem_take(&ctx->lock, K_FOREVER); #ifdef CONFIG_ADC_ASYNC ctx->asynchronous = asynchronous; ctx->signal = signal; #endif /* CONFIG_ADC_ASYNC */ } static inline void adc_context_release(struct adc_context *ctx, int status) { #ifdef CONFIG_ADC_ASYNC if (ctx->asynchronous && (status == 0)) { return; } #endif /* CONFIG_ADC_ASYNC */ k_sem_give(&ctx->lock); } static inline void adc_context_unlock_unconditionally(struct adc_context *ctx) { if (!k_sem_count_get(&ctx->lock)) { k_sem_give(&ctx->lock); } } static inline int adc_context_wait_for_completion(struct adc_context *ctx) { #ifdef CONFIG_ADC_ASYNC if (ctx->asynchronous) { return 0; } #endif /* CONFIG_ADC_ASYNC */ k_sem_take(&ctx->sync, K_FOREVER); return ctx->status; } static inline void adc_context_complete(struct adc_context *ctx, int status) { #ifdef CONFIG_ADC_ASYNC if (ctx->asynchronous) { if (ctx->signal) { k_poll_signal_raise(ctx->signal, status); } k_sem_give(&ctx->lock); return; } #endif /* CONFIG_ADC_ASYNC */ /* * Override the status only when an error is signaled to this function. * Please note that adc_context_request_next_sampling() might have set * this field. */ if (status != 0) { ctx->status = status; } k_sem_give(&ctx->sync); } static inline void adc_context_start_read(struct adc_context *ctx, const struct adc_sequence *sequence) { ctx->sequence = *sequence; ctx->status = 0; if (sequence->options) { ctx->options = *sequence->options; ctx->sequence.options = &ctx->options; ctx->sampling_index = 0U; if (ctx->options.interval_us != 0U) { atomic_set(&ctx->sampling_requested, 0); adc_context_enable_timer(ctx); return; } } adc_context_start_sampling(ctx); } /* * This function should be called after a sampling (of one or more channels, * depending on the realized sequence) is done. It calls the defined callback * function if required and takes further actions accordingly. */ static inline void adc_context_on_sampling_done(struct adc_context *ctx, struct device *dev) { if (ctx->sequence.options) { adc_sequence_callback callback = ctx->options.callback; enum adc_action action; bool finish = false; bool repeat = false; if (callback) { action = callback(dev, &ctx->sequence, ctx->sampling_index); } else { action = ADC_ACTION_CONTINUE; } switch (action) { case ADC_ACTION_REPEAT: repeat = true; break; case ADC_ACTION_FINISH: finish = true; break; default: /* ADC_ACTION_CONTINUE */ if (ctx->sampling_index < ctx->options.extra_samplings) { ++ctx->sampling_index; } else { finish = true; } } if (!finish) { adc_context_update_buffer_pointer(ctx, repeat); /* * Immediately start the next sampling if working with * a zero interval or if the timer expired again while * the current sampling was in progress. */ if (ctx->options.interval_us == 0U) { adc_context_start_sampling(ctx); } else if (atomic_dec(&ctx->sampling_requested) > 1) { adc_context_start_sampling(ctx); } return; } if (ctx->options.interval_us != 0U) { adc_context_disable_timer(ctx); } } adc_context_complete(ctx, 0); } #ifdef __cplusplus } #endif #endif /* ZEPHYR_DRIVERS_ADC_ADC_CONTEXT_H_ */