zephyr/drivers/adc/adc_nrfx_adc.c
Andrzej Głąbek 0906a51dac drivers: adc: Fix handling of invalid sampling requests
Commit aad21ecb31 introduced an incorrect
pattern of handling ADC sampling requests with invalid parameters in
both nRF ADC drivers. After discarding such request, the drivers do not
release properly the access lock and therefore become unusable.
Unfortunately, this pattern were later on copied in all other ADC
drivers in the source tree.
This commit adds the proper lock releasing in all the affected drivers.

Signed-off-by: Andrzej Głąbek <andrzej.glabek@nordicsemi.no>
2019-01-17 16:58:21 -05:00

285 lines
6.8 KiB
C

/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#define ADC_CONTEXT_USES_KERNEL_TIMER
#include "adc_context.h"
#include <nrfx_adc.h>
#define LOG_LEVEL CONFIG_ADC_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(adc_nrfx_adc);
struct driver_data {
struct adc_context ctx;
nrf_adc_value_t *buffer;
u8_t active_channels;
};
static struct driver_data m_data = {
ADC_CONTEXT_INIT_TIMER(m_data, ctx),
ADC_CONTEXT_INIT_LOCK(m_data, ctx),
ADC_CONTEXT_INIT_SYNC(m_data, ctx),
};
static nrfx_adc_channel_t m_channels[CONFIG_ADC_NRFX_ADC_CHANNEL_COUNT];
/* Implementation of the ADC driver API function: adc_channel_setup. */
static int adc_nrfx_channel_setup(struct device *dev,
const struct adc_channel_cfg *channel_cfg)
{
u8_t channel_id = channel_cfg->channel_id;
nrf_adc_config_t *config = &m_channels[channel_id].config;
if (channel_id >= CONFIG_ADC_NRFX_ADC_CHANNEL_COUNT) {
return -EINVAL;
}
if (channel_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) {
LOG_ERR("Selected ADC acquisition time is not valid");
return -EINVAL;
}
if (channel_cfg->differential) {
LOG_ERR("Differential channels are not supported");
return -EINVAL;
}
switch (channel_cfg->gain) {
case ADC_GAIN_1_3:
config->scaling = NRF_ADC_CONFIG_SCALING_INPUT_ONE_THIRD;
break;
case ADC_GAIN_2_3:
config->scaling = NRF_ADC_CONFIG_SCALING_INPUT_TWO_THIRDS;
break;
case ADC_GAIN_1:
config->scaling = NRF_ADC_CONFIG_SCALING_INPUT_FULL_SCALE;
break;
default:
LOG_ERR("Selected ADC gain is not valid");
return -EINVAL;
}
switch (channel_cfg->reference) {
case ADC_REF_INTERNAL:
config->reference = NRF_ADC_CONFIG_REF_VBG;
config->extref = NRF_ADC_CONFIG_EXTREFSEL_NONE;
break;
case ADC_REF_VDD_1_2:
config->reference = NRF_ADC_CONFIG_REF_SUPPLY_ONE_HALF;
config->extref = NRF_ADC_CONFIG_EXTREFSEL_NONE;
break;
case ADC_REF_VDD_1_3:
config->reference = NRF_ADC_CONFIG_REF_SUPPLY_ONE_THIRD;
config->extref = NRF_ADC_CONFIG_EXTREFSEL_NONE;
break;
case ADC_REF_EXTERNAL0:
config->reference = NRF_ADC_CONFIG_REF_EXT;
config->extref = NRF_ADC_CONFIG_EXTREFSEL_AREF0;
break;
case ADC_REF_EXTERNAL1:
config->reference = NRF_ADC_CONFIG_REF_EXT;
config->extref = NRF_ADC_CONFIG_EXTREFSEL_AREF1;
break;
default:
LOG_ERR("Selected ADC reference is not valid");
return -EINVAL;
}
config->input = channel_cfg->input_positive;
config->resolution = NRF_ADC_CONFIG_RES_8BIT;
return 0;
}
static void adc_context_start_sampling(struct adc_context *ctx)
{
ARG_UNUSED(ctx);
nrfx_adc_buffer_convert(m_data.buffer, m_data.active_channels);
nrfx_adc_sample();
}
static void adc_context_update_buffer_pointer(struct adc_context *ctx,
bool repeat)
{
ARG_UNUSED(ctx);
if (!repeat) {
m_data.buffer += m_data.active_channels;
}
}
static int check_buffer_size(const struct adc_sequence *sequence,
u8_t active_channels)
{
size_t needed_buffer_size;
needed_buffer_size = active_channels * sizeof(nrf_adc_value_t);
if (sequence->options) {
needed_buffer_size *= (1 + sequence->options->extra_samplings);
}
if (sequence->buffer_size < needed_buffer_size) {
LOG_ERR("Provided buffer is too small (%u/%u)",
sequence->buffer_size, needed_buffer_size);
return -ENOMEM;
}
return 0;
}
static int start_read(struct device *dev, const struct adc_sequence *sequence)
{
int error;
u32_t selected_channels = sequence->channels;
u8_t active_channels;
u8_t channel_id;
nrf_adc_config_resolution_t nrf_resolution;
/* Signal an error if channel selection is invalid (no channels or
* a non-existing one is selected).
*/
if (!selected_channels ||
(selected_channels &
~BIT_MASK(CONFIG_ADC_NRFX_ADC_CHANNEL_COUNT))) {
LOG_ERR("Invalid selection of channels");
return -EINVAL;
}
if (sequence->oversampling != 0) {
LOG_ERR("Oversampling is not supported");
return -EINVAL;
}
switch (sequence->resolution) {
case 8:
nrf_resolution = NRF_ADC_CONFIG_RES_8BIT;
break;
case 9:
nrf_resolution = NRF_ADC_CONFIG_RES_9BIT;
break;
case 10:
nrf_resolution = NRF_ADC_CONFIG_RES_10BIT;
break;
default:
LOG_ERR("ADC resolution value %d is not valid",
sequence->resolution);
return -EINVAL;
}
active_channels = 0U;
nrfx_adc_all_channels_disable();
/* Enable the channels selected for the pointed sequence.
*/
channel_id = 0U;
while (selected_channels) {
if (selected_channels & BIT(0)) {
/* The nrfx driver requires setting the resolution
* for each enabled channel individually.
*/
m_channels[channel_id].config.resolution =
nrf_resolution;
nrfx_adc_channel_enable(&m_channels[channel_id]);
++active_channels;
}
selected_channels >>= 1;
++channel_id;
}
error = check_buffer_size(sequence, active_channels);
if (error) {
return error;
}
m_data.buffer = sequence->buffer;
m_data.active_channels = active_channels;
adc_context_start_read(&m_data.ctx, sequence);
error = adc_context_wait_for_completion(&m_data.ctx);
return error;
}
/* Implementation of the ADC driver API function: adc_read. */
static int adc_nrfx_read(struct device *dev,
const struct adc_sequence *sequence)
{
int error;
adc_context_lock(&m_data.ctx, false, NULL);
error = start_read(dev, sequence);
adc_context_release(&m_data.ctx, error);
return error;
}
#ifdef CONFIG_ADC_ASYNC
/* Implementation of the ADC driver API function: adc_read_sync. */
static int adc_nrfx_read_async(struct device *dev,
const struct adc_sequence *sequence,
struct k_poll_signal *async)
{
int error;
adc_context_lock(&m_data.ctx, true, async);
error = start_read(dev, sequence);
adc_context_release(&m_data.ctx, error);
return error;
}
#endif /* CONFIG_ADC_ASYNC */
DEVICE_DECLARE(adc_0);
static void event_handler(const nrfx_adc_evt_t *p_event)
{
struct device *dev = DEVICE_GET(adc_0);
if (p_event->type == NRFX_ADC_EVT_DONE) {
adc_context_on_sampling_done(&m_data.ctx, dev);
}
}
static int init_adc(struct device *dev)
{
const nrfx_adc_config_t config = NRFX_ADC_DEFAULT_CONFIG;
nrfx_err_t result = nrfx_adc_init(&config, event_handler);
if (result != NRFX_SUCCESS) {
LOG_ERR("Failed to initialize device: %s",
dev->config->name);
return -EBUSY;
}
IRQ_CONNECT(DT_NORDIC_NRF_ADC_ADC_0_IRQ,
DT_NORDIC_NRF_ADC_ADC_0_IRQ_PRIORITY,
nrfx_isr, nrfx_adc_irq_handler, 0);
adc_context_unlock_unconditionally(&m_data.ctx);
return 0;
}
static const struct adc_driver_api adc_nrfx_driver_api = {
.channel_setup = adc_nrfx_channel_setup,
.read = adc_nrfx_read,
#ifdef CONFIG_ADC_ASYNC
.read_async = adc_nrfx_read_async,
#endif
};
#ifdef CONFIG_ADC_0
DEVICE_AND_API_INIT(adc_0, DT_NORDIC_NRF_ADC_ADC_0_LABEL,
init_adc, NULL, NULL,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&adc_nrfx_driver_api);
#endif /* CONFIG_ADC_0 */