Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2018 Nordic Semiconductor ASA |
| 3 | * |
| 4 | * SPDX-License-Identifier: Apache-2.0 |
| 5 | */ |
| 6 | |
| 7 | #define ADC_CONTEXT_USES_KERNEL_TIMER |
| 8 | #include "adc_context.h" |
| 9 | #include <nrfx_adc.h> |
| 10 | |
Anas Nashif | 9a8567f | 2018-09-17 12:00:47 -0500 | [diff] [blame] | 11 | #define LOG_LEVEL CONFIG_ADC_LOG_LEVEL |
| 12 | #include <logging/log.h> |
Andrzej Głąbek | 7c52bf9 | 2018-12-14 11:36:13 +0100 | [diff] [blame] | 13 | LOG_MODULE_REGISTER(adc_nrfx_adc); |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 14 | |
| 15 | struct driver_data { |
| 16 | struct adc_context ctx; |
| 17 | |
| 18 | nrf_adc_value_t *buffer; |
| 19 | u8_t active_channels; |
| 20 | }; |
| 21 | |
| 22 | static struct driver_data m_data = { |
| 23 | ADC_CONTEXT_INIT_TIMER(m_data, ctx), |
| 24 | ADC_CONTEXT_INIT_LOCK(m_data, ctx), |
| 25 | ADC_CONTEXT_INIT_SYNC(m_data, ctx), |
| 26 | }; |
| 27 | |
| 28 | static nrfx_adc_channel_t m_channels[CONFIG_ADC_NRFX_ADC_CHANNEL_COUNT]; |
| 29 | |
| 30 | |
| 31 | /* Implementation of the ADC driver API function: adc_channel_setup. */ |
| 32 | static int adc_nrfx_channel_setup(struct device *dev, |
| 33 | const struct adc_channel_cfg *channel_cfg) |
| 34 | { |
| 35 | u8_t channel_id = channel_cfg->channel_id; |
| 36 | nrf_adc_config_t *config = &m_channels[channel_id].config; |
| 37 | |
| 38 | if (channel_id >= CONFIG_ADC_NRFX_ADC_CHANNEL_COUNT) { |
| 39 | return -EINVAL; |
| 40 | } |
| 41 | |
| 42 | if (channel_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) { |
Anas Nashif | 9a8567f | 2018-09-17 12:00:47 -0500 | [diff] [blame] | 43 | LOG_ERR("Selected ADC acquisition time is not valid"); |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 44 | return -EINVAL; |
| 45 | } |
| 46 | |
| 47 | if (channel_cfg->differential) { |
Anas Nashif | 9a8567f | 2018-09-17 12:00:47 -0500 | [diff] [blame] | 48 | LOG_ERR("Differential channels are not supported"); |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 49 | return -EINVAL; |
| 50 | } |
| 51 | |
| 52 | switch (channel_cfg->gain) { |
| 53 | case ADC_GAIN_1_3: |
| 54 | config->scaling = NRF_ADC_CONFIG_SCALING_INPUT_ONE_THIRD; |
| 55 | break; |
| 56 | case ADC_GAIN_2_3: |
| 57 | config->scaling = NRF_ADC_CONFIG_SCALING_INPUT_TWO_THIRDS; |
| 58 | break; |
| 59 | case ADC_GAIN_1: |
| 60 | config->scaling = NRF_ADC_CONFIG_SCALING_INPUT_FULL_SCALE; |
| 61 | break; |
| 62 | default: |
Anas Nashif | 9a8567f | 2018-09-17 12:00:47 -0500 | [diff] [blame] | 63 | LOG_ERR("Selected ADC gain is not valid"); |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 64 | return -EINVAL; |
| 65 | } |
| 66 | |
| 67 | switch (channel_cfg->reference) { |
| 68 | case ADC_REF_INTERNAL: |
| 69 | config->reference = NRF_ADC_CONFIG_REF_VBG; |
| 70 | config->extref = NRF_ADC_CONFIG_EXTREFSEL_NONE; |
| 71 | break; |
| 72 | case ADC_REF_VDD_1_2: |
| 73 | config->reference = NRF_ADC_CONFIG_REF_SUPPLY_ONE_HALF; |
| 74 | config->extref = NRF_ADC_CONFIG_EXTREFSEL_NONE; |
| 75 | break; |
| 76 | case ADC_REF_VDD_1_3: |
| 77 | config->reference = NRF_ADC_CONFIG_REF_SUPPLY_ONE_THIRD; |
| 78 | config->extref = NRF_ADC_CONFIG_EXTREFSEL_NONE; |
| 79 | break; |
| 80 | case ADC_REF_EXTERNAL0: |
| 81 | config->reference = NRF_ADC_CONFIG_REF_EXT; |
| 82 | config->extref = NRF_ADC_CONFIG_EXTREFSEL_AREF0; |
| 83 | break; |
| 84 | case ADC_REF_EXTERNAL1: |
| 85 | config->reference = NRF_ADC_CONFIG_REF_EXT; |
| 86 | config->extref = NRF_ADC_CONFIG_EXTREFSEL_AREF1; |
| 87 | break; |
| 88 | default: |
Anas Nashif | 9a8567f | 2018-09-17 12:00:47 -0500 | [diff] [blame] | 89 | LOG_ERR("Selected ADC reference is not valid"); |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 90 | return -EINVAL; |
| 91 | } |
| 92 | |
| 93 | config->input = channel_cfg->input_positive; |
| 94 | |
| 95 | config->resolution = NRF_ADC_CONFIG_RES_8BIT; |
| 96 | |
| 97 | return 0; |
| 98 | } |
| 99 | |
| 100 | static void adc_context_start_sampling(struct adc_context *ctx) |
| 101 | { |
| 102 | ARG_UNUSED(ctx); |
| 103 | |
| 104 | nrfx_adc_buffer_convert(m_data.buffer, m_data.active_channels); |
| 105 | nrfx_adc_sample(); |
| 106 | } |
| 107 | |
| 108 | static void adc_context_update_buffer_pointer(struct adc_context *ctx, |
| 109 | bool repeat) |
| 110 | { |
| 111 | ARG_UNUSED(ctx); |
| 112 | |
| 113 | if (!repeat) { |
| 114 | m_data.buffer += m_data.active_channels; |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | static int check_buffer_size(const struct adc_sequence *sequence, |
| 119 | u8_t active_channels) |
| 120 | { |
| 121 | size_t needed_buffer_size; |
| 122 | |
| 123 | needed_buffer_size = active_channels * sizeof(nrf_adc_value_t); |
| 124 | if (sequence->options) { |
| 125 | needed_buffer_size *= (1 + sequence->options->extra_samplings); |
| 126 | } |
| 127 | |
| 128 | if (sequence->buffer_size < needed_buffer_size) { |
Anas Nashif | 9a8567f | 2018-09-17 12:00:47 -0500 | [diff] [blame] | 129 | LOG_ERR("Provided buffer is too small (%u/%u)", |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 130 | sequence->buffer_size, needed_buffer_size); |
| 131 | return -ENOMEM; |
| 132 | } |
| 133 | |
| 134 | return 0; |
| 135 | } |
| 136 | |
| 137 | static int start_read(struct device *dev, const struct adc_sequence *sequence) |
| 138 | { |
Andrzej Głąbek | 0906a51 | 2018-12-17 08:04:10 +0100 | [diff] [blame] | 139 | int error; |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 140 | u32_t selected_channels = sequence->channels; |
| 141 | u8_t active_channels; |
| 142 | u8_t channel_id; |
| 143 | nrf_adc_config_resolution_t nrf_resolution; |
| 144 | |
| 145 | /* Signal an error if channel selection is invalid (no channels or |
| 146 | * a non-existing one is selected). |
| 147 | */ |
| 148 | if (!selected_channels || |
| 149 | (selected_channels & |
| 150 | ~BIT_MASK(CONFIG_ADC_NRFX_ADC_CHANNEL_COUNT))) { |
Anas Nashif | 9a8567f | 2018-09-17 12:00:47 -0500 | [diff] [blame] | 151 | LOG_ERR("Invalid selection of channels"); |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 152 | return -EINVAL; |
| 153 | } |
| 154 | |
Patrik Flykt | 24d7143 | 2019-03-26 19:57:45 -0600 | [diff] [blame] | 155 | if (sequence->oversampling != 0U) { |
Anas Nashif | 9a8567f | 2018-09-17 12:00:47 -0500 | [diff] [blame] | 156 | LOG_ERR("Oversampling is not supported"); |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 157 | return -EINVAL; |
| 158 | } |
| 159 | |
| 160 | switch (sequence->resolution) { |
| 161 | case 8: |
| 162 | nrf_resolution = NRF_ADC_CONFIG_RES_8BIT; |
| 163 | break; |
| 164 | case 9: |
| 165 | nrf_resolution = NRF_ADC_CONFIG_RES_9BIT; |
| 166 | break; |
| 167 | case 10: |
| 168 | nrf_resolution = NRF_ADC_CONFIG_RES_10BIT; |
| 169 | break; |
| 170 | default: |
Anas Nashif | 9a8567f | 2018-09-17 12:00:47 -0500 | [diff] [blame] | 171 | LOG_ERR("ADC resolution value %d is not valid", |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 172 | sequence->resolution); |
| 173 | return -EINVAL; |
| 174 | } |
| 175 | |
Patrik Flykt | 8ff96b5 | 2018-11-29 11:12:22 -0800 | [diff] [blame] | 176 | active_channels = 0U; |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 177 | nrfx_adc_all_channels_disable(); |
| 178 | |
| 179 | /* Enable the channels selected for the pointed sequence. |
| 180 | */ |
Patrik Flykt | 8ff96b5 | 2018-11-29 11:12:22 -0800 | [diff] [blame] | 181 | channel_id = 0U; |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 182 | while (selected_channels) { |
| 183 | if (selected_channels & BIT(0)) { |
| 184 | /* The nrfx driver requires setting the resolution |
| 185 | * for each enabled channel individually. |
| 186 | */ |
| 187 | m_channels[channel_id].config.resolution = |
| 188 | nrf_resolution; |
| 189 | nrfx_adc_channel_enable(&m_channels[channel_id]); |
| 190 | ++active_channels; |
| 191 | } |
| 192 | selected_channels >>= 1; |
| 193 | ++channel_id; |
| 194 | } |
| 195 | |
| 196 | error = check_buffer_size(sequence, active_channels); |
| 197 | if (error) { |
| 198 | return error; |
| 199 | } |
| 200 | |
| 201 | m_data.buffer = sequence->buffer; |
| 202 | m_data.active_channels = active_channels; |
| 203 | |
| 204 | adc_context_start_read(&m_data.ctx, sequence); |
| 205 | |
Andrzej Głąbek | 0906a51 | 2018-12-17 08:04:10 +0100 | [diff] [blame] | 206 | error = adc_context_wait_for_completion(&m_data.ctx); |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 207 | return error; |
| 208 | } |
| 209 | |
| 210 | /* Implementation of the ADC driver API function: adc_read. */ |
| 211 | static int adc_nrfx_read(struct device *dev, |
| 212 | const struct adc_sequence *sequence) |
| 213 | { |
Andrzej Głąbek | 0906a51 | 2018-12-17 08:04:10 +0100 | [diff] [blame] | 214 | int error; |
| 215 | |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 216 | adc_context_lock(&m_data.ctx, false, NULL); |
Andrzej Głąbek | 0906a51 | 2018-12-17 08:04:10 +0100 | [diff] [blame] | 217 | error = start_read(dev, sequence); |
| 218 | adc_context_release(&m_data.ctx, error); |
| 219 | |
| 220 | return error; |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 221 | } |
| 222 | |
| 223 | #ifdef CONFIG_ADC_ASYNC |
| 224 | /* Implementation of the ADC driver API function: adc_read_sync. */ |
| 225 | static int adc_nrfx_read_async(struct device *dev, |
| 226 | const struct adc_sequence *sequence, |
| 227 | struct k_poll_signal *async) |
| 228 | { |
Andrzej Głąbek | 0906a51 | 2018-12-17 08:04:10 +0100 | [diff] [blame] | 229 | int error; |
| 230 | |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 231 | adc_context_lock(&m_data.ctx, true, async); |
Andrzej Głąbek | 0906a51 | 2018-12-17 08:04:10 +0100 | [diff] [blame] | 232 | error = start_read(dev, sequence); |
| 233 | adc_context_release(&m_data.ctx, error); |
| 234 | |
| 235 | return error; |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 236 | } |
Andrzej Głąbek | 0906a51 | 2018-12-17 08:04:10 +0100 | [diff] [blame] | 237 | #endif /* CONFIG_ADC_ASYNC */ |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 238 | |
| 239 | DEVICE_DECLARE(adc_0); |
| 240 | |
| 241 | static void event_handler(const nrfx_adc_evt_t *p_event) |
| 242 | { |
| 243 | struct device *dev = DEVICE_GET(adc_0); |
| 244 | |
| 245 | if (p_event->type == NRFX_ADC_EVT_DONE) { |
| 246 | adc_context_on_sampling_done(&m_data.ctx, dev); |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | static int init_adc(struct device *dev) |
| 251 | { |
| 252 | const nrfx_adc_config_t config = NRFX_ADC_DEFAULT_CONFIG; |
| 253 | |
| 254 | nrfx_err_t result = nrfx_adc_init(&config, event_handler); |
| 255 | |
| 256 | if (result != NRFX_SUCCESS) { |
Anas Nashif | 9a8567f | 2018-09-17 12:00:47 -0500 | [diff] [blame] | 257 | LOG_ERR("Failed to initialize device: %s", |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 258 | dev->config->name); |
| 259 | return -EBUSY; |
| 260 | } |
| 261 | |
Kumar Gala | dba65ce | 2019-06-19 12:55:29 -0500 | [diff] [blame] | 262 | IRQ_CONNECT(DT_NORDIC_NRF_ADC_ADC_0_IRQ_0, |
| 263 | DT_NORDIC_NRF_ADC_ADC_0_IRQ_0_PRIORITY, |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 264 | nrfx_isr, nrfx_adc_irq_handler, 0); |
| 265 | |
| 266 | adc_context_unlock_unconditionally(&m_data.ctx); |
| 267 | |
| 268 | return 0; |
| 269 | } |
| 270 | |
| 271 | static const struct adc_driver_api adc_nrfx_driver_api = { |
| 272 | .channel_setup = adc_nrfx_channel_setup, |
| 273 | .read = adc_nrfx_read, |
| 274 | #ifdef CONFIG_ADC_ASYNC |
| 275 | .read_async = adc_nrfx_read_async, |
| 276 | #endif |
Peter A. Bigot | 5a39bca | 2019-07-29 09:35:33 -0500 | [diff] [blame] | 277 | .ref_internal = 1200, |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 278 | }; |
| 279 | |
| 280 | #ifdef CONFIG_ADC_0 |
Mieszko Mierunski | b370b2b | 2018-10-03 16:07:41 +0200 | [diff] [blame] | 281 | DEVICE_AND_API_INIT(adc_0, DT_NORDIC_NRF_ADC_ADC_0_LABEL, |
Andrzej Głąbek | aad21ec | 2018-05-21 15:01:08 +0200 | [diff] [blame] | 282 | init_adc, NULL, NULL, |
| 283 | POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, |
| 284 | &adc_nrfx_driver_api); |
| 285 | #endif /* CONFIG_ADC_0 */ |