506 lines
13 KiB
C
506 lines
13 KiB
C
/* adc_dw.c - Designware ADC driver */
|
|
|
|
/*
|
|
* Copyright (c) 2015 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <errno.h>
|
|
|
|
#include <init.h>
|
|
#include <kernel.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <board.h>
|
|
#include <adc.h>
|
|
#include <arch/cpu.h>
|
|
#include "adc_dw.h"
|
|
|
|
#define ADC_CLOCK_GATE (1 << 31)
|
|
#define ADC_POWER_DOWN 0x01
|
|
#define ADC_STANDBY 0x02
|
|
#define ADC_NORMAL_WITH_CALIB 0x03
|
|
#define ADC_NORMAL_WO_CALIB 0x04
|
|
#define ADC_MODE_MASK 0x07
|
|
|
|
#define ONE_BIT_SET 0x1
|
|
#define THREE_BITS_SET 0x7
|
|
#define FIVE_BITS_SET 0x1f
|
|
#define SIX_BITS_SET 0x3f
|
|
#define SEVEN_BITS_SET 0xef
|
|
#define ELEVEN_BITS_SET 0x7ff
|
|
|
|
#define INPUT_MODE_POS 5
|
|
#define CAPTURE_MODE_POS 6
|
|
#define OUTPUT_MODE_POS 7
|
|
#define SERIAL_DELAY_POS 8
|
|
#define SEQUENCE_MODE_POS 13
|
|
#define SEQ_ENTRIES_POS 16
|
|
#define THRESHOLD_POS 24
|
|
|
|
#define SEQ_DELAY_EVEN_POS 5
|
|
#define SEQ_MUX_ODD_POS 16
|
|
#define SEQ_DELAY_ODD_POS 21
|
|
|
|
#ifdef CONFIG_SOC_QUARK_SE_C1000_SS
|
|
#define int_unmask(__mask) \
|
|
sys_write32(sys_read32((__mask)) & ENABLE_SSS_INTERRUPTS, (__mask))
|
|
#else
|
|
#define int_unmask(...) { ; }
|
|
#endif
|
|
static void adc_config_irq(void);
|
|
|
|
#ifdef CONFIG_ADC_DW_CALIBRATION
|
|
static void calibration_command(u8_t command)
|
|
{
|
|
u32_t state;
|
|
u32_t reg_value;
|
|
|
|
state = irq_lock();
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_MST0);
|
|
reg_value |= (command & THREE_BITS_SET) << 17;
|
|
reg_value |= 0x10000;
|
|
sys_out32(reg_value, PERIPH_ADDR_BASE_CREG_MST0);
|
|
irq_unlock(state);
|
|
/*Poll waiting for command*/
|
|
do {
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_SLV0);
|
|
} while ((reg_value & 0x10) == 0);
|
|
|
|
/*Clear Calibration Request*/
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_MST0);
|
|
reg_value &= ~(0x10000);
|
|
sys_out32(reg_value, PERIPH_ADDR_BASE_CREG_MST0);
|
|
}
|
|
|
|
static void adc_goto_normal_mode(struct device *dev)
|
|
{
|
|
struct adc_info *info = dev->driver_data;
|
|
u8_t calibration_value;
|
|
u32_t reg_value;
|
|
u32_t state;
|
|
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_SLV0);
|
|
|
|
if (((reg_value & 0xE) >> 1) != ADC_NORMAL_WITH_CALIB) {
|
|
|
|
state = irq_lock();
|
|
/*Request Normal With Calibration Mode*/
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_MST0);
|
|
reg_value &= ~(ADC_MODE_MASK);
|
|
reg_value |= ADC_NORMAL_WITH_CALIB;
|
|
sys_out32(reg_value, PERIPH_ADDR_BASE_CREG_MST0);
|
|
irq_unlock(state);
|
|
|
|
/*Poll waiting for normal mode*/
|
|
do {
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_SLV0);
|
|
} while ((reg_value & 0x1) == 0);
|
|
|
|
if (info->calibration_value == ADC_NONE_CALIBRATION) {
|
|
/*Reset Calibration*/
|
|
calibration_command(ADC_CMD_RESET_CALIBRATION);
|
|
/*Request Calibration*/
|
|
calibration_command(ADC_CMD_START_CALIBRATION);
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_SLV0);
|
|
calibration_value = (reg_value >> 5) & SEVEN_BITS_SET;
|
|
info->calibration_value = calibration_value;
|
|
}
|
|
|
|
/*Load Calibration*/
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_MST0);
|
|
reg_value |= (info->calibration_value << 20);
|
|
sys_out32(reg_value, PERIPH_ADDR_BASE_CREG_MST0);
|
|
calibration_command(ADC_CMD_LOAD_CALIBRATION);
|
|
}
|
|
}
|
|
|
|
#else
|
|
static void adc_goto_normal_mode(struct device *dev)
|
|
{
|
|
u32_t reg_value;
|
|
u32_t state;
|
|
|
|
ARG_UNUSED(dev);
|
|
reg_value = sys_in32(
|
|
PERIPH_ADDR_BASE_CREG_SLV0 + SLV_OBSR);
|
|
|
|
if (((reg_value & 0xE) >> 1) == ADC_NORMAL_WO_CALIB) {
|
|
state = irq_lock();
|
|
/*Request Power Down*/
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_MST0);
|
|
|
|
reg_value &= ~(ADC_MODE_MASK);
|
|
reg_value |= ADC_POWER_DOWN;
|
|
|
|
sys_out32(reg_value, PERIPH_ADDR_BASE_CREG_MST0);
|
|
irq_unlock(state);
|
|
|
|
do {
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_SLV0);
|
|
} while ((reg_value & 0x1) == 0);
|
|
}
|
|
|
|
/*Request Normal With Calibration Mode*/
|
|
state = irq_lock();
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_MST0);
|
|
reg_value &= ~(ADC_MODE_MASK);
|
|
reg_value |= ADC_NORMAL_WO_CALIB;
|
|
sys_out32(reg_value, PERIPH_ADDR_BASE_CREG_MST0);
|
|
irq_unlock(state);
|
|
|
|
/*Poll waiting for normal mode*/
|
|
do {
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_SLV0);
|
|
} while ((reg_value & 0x1) == 0);
|
|
}
|
|
#endif
|
|
|
|
static void adc_goto_deep_power_down(void)
|
|
{
|
|
u32_t reg_value;
|
|
u32_t state;
|
|
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_SLV0);
|
|
if ((reg_value & 0xE >> 1) != 0) {
|
|
state = irq_lock();
|
|
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_MST0);
|
|
|
|
reg_value &= ~(ADC_MODE_MASK);
|
|
|
|
reg_value |= 0 | ADC_CLOCK_GATE;
|
|
sys_out32(reg_value, PERIPH_ADDR_BASE_CREG_MST0);
|
|
|
|
irq_unlock(state);
|
|
do {
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_SLV0);
|
|
} while ((reg_value & 0x1) == 0);
|
|
}
|
|
}
|
|
|
|
static void adc_dw_enable(struct device *dev)
|
|
{
|
|
u32_t reg_value;
|
|
struct adc_info *info = dev->driver_data;
|
|
const struct adc_config *config = dev->config->config_info;
|
|
u32_t adc_base = config->reg_base;
|
|
|
|
/*Go to Normal Mode*/
|
|
sys_out32(ADC_INT_DSB|ENABLE_ADC, adc_base + ADC_CTRL);
|
|
adc_goto_normal_mode(dev);
|
|
|
|
/*Clock Gate*/
|
|
reg_value = sys_in32(PERIPH_ADDR_BASE_CREG_MST0);
|
|
reg_value &= ~(ADC_CLOCK_GATE);
|
|
sys_out32(reg_value, PERIPH_ADDR_BASE_CREG_MST0);
|
|
sys_out32(ENABLE_ADC, adc_base + ADC_CTRL);
|
|
|
|
info->state = ADC_STATE_IDLE;
|
|
}
|
|
|
|
static void adc_dw_disable(struct device *dev)
|
|
{
|
|
u32_t saved;
|
|
struct adc_info *info = dev->driver_data;
|
|
const struct adc_config *config = dev->config->config_info;
|
|
u32_t adc_base = config->reg_base;
|
|
|
|
sys_out32(ADC_INT_DSB|ENABLE_ADC, adc_base + ADC_CTRL);
|
|
adc_goto_deep_power_down();
|
|
sys_out32(ADC_INT_DSB|ADC_SEQ_PTR_RST, adc_base + ADC_CTRL);
|
|
|
|
saved = irq_lock();
|
|
|
|
sys_out32(sys_in32(adc_base + ADC_SET)|ADC_FLUSH_RX, adc_base + ADC_SET);
|
|
irq_unlock(saved);
|
|
|
|
info->state = ADC_STATE_DISABLED;
|
|
}
|
|
|
|
static int adc_dw_read_request(struct device *dev, struct adc_seq_table *seq_tbl)
|
|
{
|
|
u32_t i;
|
|
u32_t ctrl;
|
|
u32_t tmp_val;
|
|
u32_t num_iters;
|
|
u32_t saved;
|
|
struct adc_seq_entry *entry;
|
|
struct adc_info *info = dev->driver_data;
|
|
const struct adc_config *config = dev->config->config_info;
|
|
u32_t adc_base = config->reg_base;
|
|
|
|
if (info->state != ADC_STATE_IDLE) {
|
|
return 1;
|
|
}
|
|
|
|
saved = irq_lock();
|
|
info->seq_size = seq_tbl->num_entries;
|
|
|
|
ctrl = sys_in32(adc_base + ADC_CTRL);
|
|
ctrl |= ADC_SEQ_PTR_RST;
|
|
sys_out32(ctrl, adc_base + ADC_CTRL);
|
|
|
|
tmp_val = sys_in32(adc_base + ADC_SET);
|
|
tmp_val &= ADC_SEQ_SIZE_SET_MASK;
|
|
tmp_val |= (((seq_tbl->num_entries - 1) & SIX_BITS_SET)
|
|
<< SEQ_ENTRIES_POS);
|
|
tmp_val |= ((seq_tbl->num_entries - 1) << THRESHOLD_POS);
|
|
sys_out32(tmp_val, adc_base + ADC_SET);
|
|
|
|
irq_unlock(saved);
|
|
|
|
num_iters = seq_tbl->num_entries/2;
|
|
|
|
for (i = 0, entry = seq_tbl->entries;
|
|
i < num_iters; i++, entry += 2) {
|
|
tmp_val = ((entry[1].sampling_delay & ELEVEN_BITS_SET)
|
|
<< SEQ_DELAY_ODD_POS);
|
|
tmp_val |= ((entry[1].channel_id & FIVE_BITS_SET)
|
|
<< SEQ_MUX_ODD_POS);
|
|
tmp_val |= ((entry[0].sampling_delay & ELEVEN_BITS_SET)
|
|
<< SEQ_DELAY_EVEN_POS);
|
|
tmp_val |= (entry[0].channel_id & FIVE_BITS_SET);
|
|
sys_out32(tmp_val, adc_base + ADC_SEQ);
|
|
}
|
|
|
|
if ((seq_tbl->num_entries % 2) != 0) {
|
|
tmp_val = ((entry[0].sampling_delay & ELEVEN_BITS_SET)
|
|
<< SEQ_DELAY_EVEN_POS);
|
|
tmp_val |= (entry[0].channel_id & FIVE_BITS_SET);
|
|
sys_out32(tmp_val, adc_base + ADC_SEQ);
|
|
}
|
|
|
|
sys_out32(ctrl | ADC_SEQ_PTR_RST, adc_base + ADC_CTRL);
|
|
|
|
info->entries = seq_tbl->entries;
|
|
#ifdef CONFIG_ADC_DW_REPETITIVE
|
|
memset(info->index, 0, seq_tbl->num_entries);
|
|
#endif
|
|
info->state = ADC_STATE_SAMPLING;
|
|
sys_out32(START_ADC_SEQ, adc_base + ADC_CTRL);
|
|
|
|
k_sem_take(&info->device_sync_sem, K_FOREVER);
|
|
|
|
if (info->state == ADC_STATE_ERROR) {
|
|
info->state = ADC_STATE_IDLE;
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int adc_dw_read(struct device *dev, struct adc_seq_table *seq_tbl)
|
|
{
|
|
struct adc_info *info = dev->driver_data;
|
|
|
|
#ifdef CONFIG_ADC_DW_DUMMY_CONVERSION
|
|
if (info->dummy_conversion == ADC_NONE_DUMMY) {
|
|
adc_dw_read_request(dev, seq_tbl);
|
|
info->dummy_conversion = ADC_DONE_DUMMY;
|
|
}
|
|
#endif
|
|
return adc_dw_read_request(dev, seq_tbl);
|
|
}
|
|
|
|
static struct adc_driver_api api_funcs = {
|
|
.enable = adc_dw_enable,
|
|
.disable = adc_dw_disable,
|
|
.read = adc_dw_read,
|
|
};
|
|
|
|
int adc_dw_init(struct device *dev)
|
|
{
|
|
u32_t tmp_val;
|
|
u32_t val;
|
|
const struct adc_config *config = dev->config->config_info;
|
|
u32_t adc_base = config->reg_base;
|
|
struct adc_info *info = dev->driver_data;
|
|
|
|
sys_out32(ADC_INT_DSB | ADC_CLK_ENABLE, adc_base + ADC_CTRL);
|
|
|
|
tmp_val = sys_in32(adc_base + ADC_SET);
|
|
tmp_val &= ADC_CONFIG_SET_MASK;
|
|
val = (config->sample_width) & FIVE_BITS_SET;
|
|
val &= ~(1 << INPUT_MODE_POS);
|
|
val |= ((config->capture_mode & ONE_BIT_SET) << CAPTURE_MODE_POS);
|
|
val |= ((config->out_mode & ONE_BIT_SET) << OUTPUT_MODE_POS);
|
|
val |= ((config->serial_dly & FIVE_BITS_SET) << SERIAL_DELAY_POS);
|
|
val |= ((config->seq_mode & ONE_BIT_SET) << SEQUENCE_MODE_POS);
|
|
sys_out32(tmp_val|val, adc_base + ADC_SET);
|
|
|
|
sys_out32(config->clock_ratio & ADC_CLK_RATIO_MASK,
|
|
adc_base + ADC_DIVSEQSTAT);
|
|
|
|
sys_out32(ADC_INT_ENABLE & ~(ADC_CLK_ENABLE),
|
|
adc_base + ADC_CTRL);
|
|
|
|
config->config_func();
|
|
|
|
k_sem_init(&info->device_sync_sem, 0, UINT_MAX);
|
|
|
|
int_unmask(config->reg_irq_mask);
|
|
int_unmask(config->reg_err_mask);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_ADC_DW_SINGLESHOT
|
|
static void adc_dw_rx_isr(void *arg)
|
|
{
|
|
struct device *dev = (struct device *)arg;
|
|
struct device_config *dev_config = dev->config;
|
|
const struct adc_config *config = dev_config->config_info;
|
|
struct adc_info *info = dev->driver_data;
|
|
u32_t adc_base = config->reg_base;
|
|
struct adc_seq_entry *entries = info->entries;
|
|
u32_t reg_val;
|
|
u32_t seq_index;
|
|
|
|
for (seq_index = 0; seq_index < info->seq_size; seq_index++) {
|
|
u32_t *adc_buffer;
|
|
|
|
reg_val = sys_in32(adc_base + ADC_SET);
|
|
sys_out32(reg_val|ADC_POP_SAMPLE, adc_base + ADC_SET);
|
|
adc_buffer = (u32_t *)entries[seq_index].buffer;
|
|
*adc_buffer = sys_in32(adc_base + ADC_SAMPLE);
|
|
}
|
|
|
|
/*Resume ADC state to continue new conversions*/
|
|
sys_out32(RESUME_ADC_CAPTURE, adc_base + ADC_CTRL);
|
|
reg_val = sys_in32(adc_base + ADC_SET);
|
|
sys_out32(reg_val | ADC_FLUSH_RX, adc_base + ADC_SET);
|
|
info->state = ADC_STATE_IDLE;
|
|
|
|
/*Clear data A register*/
|
|
reg_val = sys_in32(adc_base + ADC_CTRL);
|
|
sys_out32(reg_val | ADC_CLR_DATA_A, adc_base + ADC_CTRL);
|
|
|
|
k_sem_give(&info->device_sync_sem);
|
|
}
|
|
#else /*CONFIG_ADC_DW_REPETITIVE*/
|
|
static void adc_dw_rx_isr(void *arg)
|
|
{
|
|
struct device *dev = (struct device *)arg;
|
|
struct device_config *dev_config = dev->config;
|
|
const struct adc_config *config = dev_config->config_info;
|
|
struct adc_info *info = dev->driver_data;
|
|
u32_t adc_base = config->reg_base;
|
|
struct adc_seq_entry *entries = info->entries;
|
|
u32_t reg_val;
|
|
u32_t sequence_index;
|
|
u8_t full_buffer_flag = 0;
|
|
|
|
for (sequence_index = 0; sequence_index < info->seq_size; sequence_index++) {
|
|
u32_t *adc_buffer;
|
|
u32_t repetitive_index;
|
|
|
|
repetitive_index = info->index[sequence_index];
|
|
/*API array is 8 bits array but ADC reads blocks of 32 bits with every sample.*/
|
|
if (repetitive_index >= (entries[sequence_index].buffer_length >> 2)) {
|
|
full_buffer_flag = 1;
|
|
continue;
|
|
}
|
|
|
|
reg_val = sys_in32(adc_base + ADC_SET);
|
|
sys_out32(reg_val|ADC_POP_SAMPLE, adc_base + ADC_SET);
|
|
adc_buffer = (u32_t *)entries[sequence_index].buffer;
|
|
adc_buffer[repetitive_index] = sys_in32(adc_base + ADC_SAMPLE);
|
|
repetitive_index++;
|
|
info->index[sequence_index] = repetitive_index;
|
|
}
|
|
|
|
if (full_buffer_flag == 1) {
|
|
/*Resume ADC state to continue new conversions*/
|
|
sys_out32(RESUME_ADC_CAPTURE, adc_base + ADC_CTRL);
|
|
reg_val = sys_in32(adc_base + ADC_SET);
|
|
sys_out32(reg_val | ADC_FLUSH_RX, adc_base + ADC_SET);
|
|
info->state = ADC_STATE_IDLE;
|
|
/*Clear data A register*/
|
|
reg_val = sys_in32(adc_base + ADC_CTRL);
|
|
sys_out32(reg_val | ADC_CLR_DATA_A, adc_base + ADC_CTRL);
|
|
|
|
k_sem_give(&info->device_sync_sem);
|
|
return;
|
|
}
|
|
|
|
/*Clear data A register*/
|
|
reg_val = sys_in32(adc_base + ADC_CTRL);
|
|
sys_out32(reg_val | ADC_CLR_DATA_A, adc_base + ADC_CTRL);
|
|
}
|
|
#endif
|
|
|
|
|
|
static void adc_dw_err_isr(void *arg)
|
|
{
|
|
struct device *dev = (struct device *) arg;
|
|
const struct adc_config *config = dev->config->config_info;
|
|
struct adc_info *info = dev->driver_data;
|
|
u32_t adc_base = config->reg_base;
|
|
u32_t reg_val = sys_in32(adc_base + ADC_SET);
|
|
|
|
|
|
sys_out32(RESUME_ADC_CAPTURE, adc_base + ADC_CTRL);
|
|
sys_out32(reg_val | ADC_FLUSH_RX, adc_base + ADC_CTRL);
|
|
sys_out32(FLUSH_ADC_ERRORS, adc_base + ADC_CTRL);
|
|
|
|
info->state = ADC_STATE_ERROR;
|
|
|
|
k_sem_give(&info->device_sync_sem);
|
|
}
|
|
|
|
#ifdef CONFIG_ADC_DW
|
|
|
|
struct adc_info adc_info_dev = {
|
|
.state = ADC_STATE_IDLE,
|
|
#ifdef CONFIG_ADC_DW_CALIBRATION
|
|
.calibration_value = ADC_NONE_CALIBRATION,
|
|
#endif
|
|
#ifdef CONFIG_ADC_DW_DUMMY_CONVERSION
|
|
.dummy_conversion = ADC_NONE_DUMMY,
|
|
#endif
|
|
};
|
|
|
|
static struct adc_config adc_config_dev = {
|
|
.reg_base = PERIPH_ADDR_BASE_ADC,
|
|
.reg_irq_mask = SCSS_REGISTER_BASE + INT_SS_ADC_IRQ_MASK,
|
|
.reg_err_mask = SCSS_REGISTER_BASE + INT_SS_ADC_ERR_MASK,
|
|
#ifdef CONFIG_ADC_DW_SERIAL
|
|
.out_mode = 0,
|
|
#elif CONFIG_ADC_DW_PARALLEL
|
|
.out_mode = 1,
|
|
#endif
|
|
#ifdef CONFIG_ADC_DW_SINGLESHOT
|
|
.seq_mode = 0,
|
|
#elif CONFIG_ADC_DW_REPETITIVE
|
|
.seq_mode = 1,
|
|
#endif
|
|
#ifdef CONFIG_ADC_DW_RISING_EDGE
|
|
.capture_mode = 0,
|
|
#elif CONFIG_ADC_DW_FALLING_EDGE
|
|
.capture_mode = 1,
|
|
#endif
|
|
.sample_width = CONFIG_ADC_DW_SAMPLE_WIDTH,
|
|
.clock_ratio = CONFIG_ADC_DW_CLOCK_RATIO,
|
|
.serial_dly = CONFIG_ADC_DW_SERIAL_DELAY,
|
|
.config_func = adc_config_irq,
|
|
};
|
|
|
|
DEVICE_AND_API_INIT(adc_dw, CONFIG_ADC_0_NAME, &adc_dw_init,
|
|
&adc_info_dev, &adc_config_dev,
|
|
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
|
|
&api_funcs);
|
|
|
|
static void adc_config_irq(void)
|
|
{
|
|
IRQ_CONNECT(IRQ_ADC_IRQ, CONFIG_ADC_0_IRQ_PRI, adc_dw_rx_isr,
|
|
DEVICE_GET(adc_dw), 0);
|
|
irq_enable(IRQ_ADC_IRQ);
|
|
|
|
IRQ_CONNECT(IRQ_ADC_ERR, CONFIG_ADC_0_IRQ_PRI,
|
|
adc_dw_err_isr, DEVICE_GET(adc_dw), 0);
|
|
irq_enable(IRQ_ADC_ERR);
|
|
}
|
|
#endif
|