| /* adc_qmsi.c - QMSI ADC driver */ |
| |
| /* |
| * Copyright (c) 2016 Intel Corporation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <errno.h> |
| |
| #include <init.h> |
| #include <nanokernel.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <board.h> |
| #include <adc.h> |
| #include <arch/cpu.h> |
| #include <atomic.h> |
| |
| #include "qm_adc.h" |
| #include "qm_scss.h" |
| |
| enum { |
| ADC_STATE_IDLE, |
| ADC_STATE_BUSY, |
| ADC_STATE_ERROR |
| }; |
| |
| struct adc_info { |
| atomic_t state; |
| device_sync_call_t sync; |
| struct nano_sem sem; |
| }; |
| |
| static struct adc_info *adc_context; |
| |
| static void adc_config_irq(void); |
| |
| static void complete_callback(void) |
| { |
| if (adc_context) { |
| device_sync_call_complete(&adc_context->sync); |
| } |
| } |
| |
| static void error_callback(void) |
| { |
| if (adc_context) { |
| adc_context->state = ADC_STATE_ERROR; |
| device_sync_call_complete(&adc_context->sync); |
| } |
| } |
| |
| static void adc_lock(struct adc_info *data) |
| { |
| nano_sem_take(&data->sem, TICKS_UNLIMITED); |
| data->state = ADC_STATE_BUSY; |
| |
| } |
| static void adc_unlock(struct adc_info *data) |
| { |
| nano_sem_give(&data->sem); |
| data->state = ADC_STATE_IDLE; |
| |
| } |
| #if (CONFIG_ADC_QMSI_CALIBRATION) |
| static void adc_qmsi_enable(struct device *dev) |
| { |
| struct adc_info *info = dev->driver_data; |
| |
| adc_lock(info); |
| qm_adc_set_mode(QM_ADC_0, QM_ADC_MODE_NORM_CAL); |
| qm_adc_calibrate(QM_ADC_0); |
| adc_unlock(info); |
| } |
| |
| #else |
| static void adc_qmsi_enable(struct device *dev) |
| { |
| struct adc_info *info = dev->driver_data; |
| |
| adc_lock(info); |
| qm_adc_set_mode(QM_ADC_0, QM_ADC_MODE_NORM_NO_CAL); |
| adc_unlock(info); |
| } |
| #endif /* CONFIG_ADC_QMSI_CALIBRATION */ |
| |
| static void adc_qmsi_disable(struct device *dev) |
| { |
| struct adc_info *info = dev->driver_data; |
| |
| adc_lock(info); |
| /* Go to deep sleep */ |
| qm_adc_set_mode(QM_ADC_0, QM_ADC_MODE_DEEP_PWR_DOWN); |
| adc_unlock(info); |
| } |
| |
| #if (CONFIG_ADC_QMSI_POLL) |
| static int adc_qmsi_read(struct device *dev, struct adc_seq_table *seq_tbl) |
| { |
| int i, ret = 0; |
| qm_adc_xfer_t xfer; |
| qm_adc_config_t cfg; |
| |
| struct adc_info *info = dev->driver_data; |
| |
| if (qm_adc_get_config(QM_ADC_0, &cfg) != QM_RC_OK) { |
| return -ENOTSUP; |
| } |
| |
| for (i = 0; i < seq_tbl->num_entries; i++) { |
| |
| xfer.ch = (qm_adc_channel_t *)&seq_tbl->entries[i].channel_id; |
| /* Just one channel at the time using the Zephyr sequence table */ |
| xfer.ch_len = 1; |
| xfer.samples = (uint32_t *)seq_tbl->entries[i].buffer; |
| |
| /* buffer length (bytes) the number of samples, the QMSI Driver does |
| * not allow more than QM_ADC_FIFO_LEN samples at the time in polling |
| * mode, if that happens, the qm_adc_convert api will return with an |
| * error |
| */ |
| xfer.samples_len = (seq_tbl->entries[i].buffer_length); |
| xfer.complete_callback = NULL; |
| xfer.error_callback = NULL; |
| |
| cfg.window = seq_tbl->entries[i].sampling_delay; |
| |
| adc_lock(info); |
| |
| if (qm_adc_set_config(QM_ADC_0, &cfg) != QM_RC_OK) { |
| ret = -EINVAL; |
| adc_unlock(info); |
| break; |
| } |
| |
| /* Run the conversion, here the function will poll for the samples |
| * The function will constantly read the status register to check if |
| * the number of samples required has been captured |
| */ |
| if (qm_adc_convert(QM_ADC_0, &xfer) != QM_RC_OK) { |
| ret = -EIO; |
| adc_unlock(info); |
| break; |
| } |
| |
| /* Successful Analog to Digital conversion */ |
| adc_unlock(info); |
| } |
| |
| return ret; |
| } |
| #else |
| static int adc_qmsi_read(struct device *dev, struct adc_seq_table *seq_tbl) |
| { |
| int i, ret = 0; |
| qm_adc_xfer_t xfer; |
| qm_adc_config_t cfg; |
| |
| struct adc_info *info = dev->driver_data; |
| |
| if (qm_adc_get_config(QM_ADC_0, &cfg) != QM_RC_OK) { |
| return -ENOTSUP; |
| } |
| |
| for (i = 0; i < seq_tbl->num_entries; i++) { |
| |
| xfer.ch = (qm_adc_channel_t *)&seq_tbl->entries[i].channel_id; |
| /* Just one channel at the time using the Zephyr sequence table */ |
| xfer.ch_len = 1; |
| xfer.samples = (uint32_t *)seq_tbl->entries[i].buffer; |
| xfer.samples_len = (seq_tbl->entries[i].buffer_length) >> 2; |
| xfer.complete_callback = complete_callback; |
| xfer.error_callback = error_callback; |
| |
| cfg.window = seq_tbl->entries[i].sampling_delay; |
| |
| adc_lock(info); |
| |
| if (qm_adc_set_config(QM_ADC_0, &cfg) != QM_RC_OK) { |
| ret = -EINVAL; |
| adc_unlock(info); |
| break; |
| } |
| |
| /* ADC info used by the callbacks */ |
| adc_context = info; |
| |
| /* This is the interrupt driven API, will generate and interrupt and |
| * call the complete_callback function once the samples have been |
| * obtained |
| */ |
| if (qm_adc_irq_convert(QM_ADC_0, &xfer) != QM_RC_OK) { |
| adc_context = NULL; |
| ret = -EIO; |
| adc_unlock(info); |
| break; |
| } |
| |
| /* Wait for the interrupt to finish */ |
| device_sync_call_wait(&info->sync); |
| |
| if (info->state == ADC_STATE_ERROR) { |
| ret = -EIO; |
| adc_unlock(info); |
| break; |
| } |
| adc_context = NULL; |
| |
| /* Successful Analog to Digital conversion */ |
| adc_unlock(info); |
| } |
| |
| return ret; |
| } |
| #endif /* CONFIG_ADC_QMSI_POLL */ |
| |
| void adc_qmsi_isr(void *arg) |
| { |
| qm_adc_0_isr(); |
| } |
| |
| static struct adc_driver_api api_funcs = { |
| .enable = adc_qmsi_enable, |
| .disable = adc_qmsi_disable, |
| .read = adc_qmsi_read, |
| }; |
| |
| int adc_qmsi_init(struct device *dev) |
| { |
| qm_adc_config_t cfg; |
| |
| struct adc_info *info = dev->driver_data; |
| |
| dev->driver_api = &api_funcs; |
| |
| /* Enable the ADC and set the clock divisor */ |
| clk_periph_enable(CLK_PERIPH_CLK | CLK_PERIPH_ADC | |
| CLK_PERIPH_ADC_REGISTER); |
| /* ADC clock divider*/ |
| clk_adc_set_div(CONFIG_ADC_QMSI_CLOCK_RATIO); |
| |
| /* Set up config */ |
| /* Clock cycles between the start of each sample */ |
| cfg.window = CONFIG_ADC_QMSI_SERIAL_DELAY; |
| cfg.resolution = CONFIG_ADC_QMSI_SAMPLE_WIDTH; |
| |
| qm_adc_set_config(QM_ADC_0, &cfg); |
| |
| device_sync_call_init(&info->sync); |
| |
| nano_sem_init(&info->sem); |
| nano_sem_give(&info->sem); |
| info->state = ADC_STATE_IDLE; |
| |
| adc_config_irq(); |
| |
| return 0; |
| } |
| |
| struct adc_info adc_info_dev; |
| |
| DEVICE_INIT(adc_qmsi, CONFIG_ADC_QMSI_0_DRV_NAME, &adc_qmsi_init, |
| &adc_info_dev, NULL, |
| SECONDARY, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); |
| |
| static void adc_config_irq(void) |
| { |
| IRQ_CONNECT(QM_IRQ_ADC_0, CONFIG_ADC_0_IRQ_PRI, qm_adc_0_isr, |
| NULL, (IOAPIC_LEVEL | IOAPIC_HIGH)); |
| |
| irq_enable(QM_IRQ_ADC_0); |
| |
| QM_SCSS_INT->int_adc_calib_mask &= ~BIT(0); |
| } |