522 lines
13 KiB
C
522 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 <soc.h>
|
|
#include <adc.h>
|
|
#include <arch/cpu.h>
|
|
|
|
#define ADC_CONTEXT_USES_KERNEL_TIMER
|
|
#include "adc_context.h"
|
|
#include "adc_dw.h"
|
|
|
|
#define LOG_LEVEL CONFIG_ADC_LOG_LEVEL
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(adc_dw);
|
|
|
|
#define ADC_CLOCK_GATE (1 << 31)
|
|
#define ADC_DEEP_POWER_DOWN 0x01
|
|
#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);
|
|
|
|
|
|
struct adc_info adc_info_dev = {
|
|
ADC_CONTEXT_INIT_TIMER(adc_info_dev, ctx),
|
|
ADC_CONTEXT_INIT_LOCK(adc_info_dev, ctx),
|
|
ADC_CONTEXT_INIT_SYNC(adc_info_dev, ctx),
|
|
.state = ADC_STATE_IDLE,
|
|
#ifdef CONFIG_ADC_DW_CALIBRATION
|
|
.calibration_value = ADC_NONE_CALIBRATION,
|
|
#endif
|
|
};
|
|
|
|
#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 & BIT(4)) == 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 & ADC_MODE_MASK) != 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 & BIT(3)) == 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);
|
|
|
|
if ((reg_value & ADC_MODE_MASK) == 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 & BIT(3)) == 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 & BIT(3)) == 0);
|
|
}
|
|
#endif
|
|
|
|
static int set_resolution(struct device *dev,
|
|
const struct adc_sequence *sequence)
|
|
{
|
|
u32_t tmp_val;
|
|
const struct adc_config *config = dev->config->config_info;
|
|
u32_t adc_base = config->reg_base;
|
|
|
|
tmp_val = sys_in32(adc_base + ADC_SET);
|
|
tmp_val &= ~FIVE_BITS_SET;
|
|
|
|
switch (sequence->resolution) {
|
|
case 6:
|
|
break;
|
|
case 8:
|
|
tmp_val |= 1 & FIVE_BITS_SET;
|
|
break;
|
|
case 10:
|
|
tmp_val |= 2 & FIVE_BITS_SET;
|
|
break;
|
|
case 12:
|
|
tmp_val |= 3 & FIVE_BITS_SET;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
sys_out32(tmp_val, adc_base + ADC_SET);
|
|
|
|
return 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;
|
|
}
|
|
|
|
/* Implementation of the ADC driver API function: adc_channel_setup. */
|
|
static int adc_dw_channel_setup(struct device *dev,
|
|
const struct adc_channel_cfg *channel_cfg)
|
|
{
|
|
u8_t channel_id = channel_cfg->channel_id;
|
|
struct adc_info *info = dev->driver_data;
|
|
|
|
if (channel_id >= DW_CHANNEL_COUNT) {
|
|
LOG_ERR("Invalid channel id");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (channel_cfg->gain != ADC_GAIN_1) {
|
|
LOG_ERR("Invalid channel gain");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (channel_cfg->reference != ADC_REF_INTERNAL) {
|
|
LOG_ERR("Invalid channel reference");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (channel_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) {
|
|
LOG_ERR("Invalid channel acquisition time");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (info->state != ADC_STATE_IDLE) {
|
|
LOG_ERR("ADC is busy or in error state");
|
|
return -EAGAIN;
|
|
}
|
|
|
|
info->active_channels |= 1 << channel_id;
|
|
return 0;
|
|
}
|
|
|
|
static int adc_dw_read_request(struct device *dev,
|
|
const struct adc_sequence *seq_tbl)
|
|
{
|
|
struct adc_info *info = dev->driver_data;
|
|
int error = 0;
|
|
u32_t saved;
|
|
|
|
/*hardware requires minimum 10 us delay between consecutive samples*/
|
|
if (seq_tbl->options &&
|
|
seq_tbl->options->extra_samplings &&
|
|
seq_tbl->options->interval_us < 10) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
info->channels = seq_tbl->channels & info->active_channels;
|
|
|
|
if (seq_tbl->channels != info->channels) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
error = set_resolution(dev, seq_tbl);
|
|
if (error) {
|
|
return error;
|
|
}
|
|
|
|
saved = irq_lock();
|
|
info->entries = seq_tbl;
|
|
info->buffer = (u16_t *)seq_tbl->buffer;
|
|
|
|
if (seq_tbl->options) {
|
|
info->seq_size = seq_tbl->options->extra_samplings + 1;
|
|
} else {
|
|
info->seq_size = 1U;
|
|
}
|
|
|
|
info->state = ADC_STATE_SAMPLING;
|
|
irq_unlock(saved);
|
|
|
|
adc_context_start_read(&info->ctx, seq_tbl);
|
|
error = adc_context_wait_for_completion(&info->ctx);
|
|
|
|
if (info->state == ADC_STATE_ERROR) {
|
|
info->state = ADC_STATE_IDLE;
|
|
return -EIO;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static int adc_dw_read(struct device *dev, const struct adc_sequence *seq_tbl)
|
|
{
|
|
struct adc_info *info = dev->driver_data;
|
|
int ret;
|
|
|
|
adc_context_lock(&info->ctx, false, NULL);
|
|
ret = adc_dw_read_request(dev, seq_tbl);
|
|
adc_context_release(&info->ctx, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_ADC_ASYNC
|
|
/* Implementation of the ADC driver API function: adc_read_async. */
|
|
static int adc_dw_read_async(struct device *dev,
|
|
const struct adc_sequence *sequence,
|
|
struct k_poll_signal *async)
|
|
{
|
|
struct adc_info *info = dev->driver_data;
|
|
int ret;
|
|
|
|
adc_context_lock(&info->ctx, true, async);
|
|
ret = adc_dw_read_request(dev, sequence);
|
|
adc_context_release(&info->ctx, ret);
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static void adc_dw_start_conversion(struct device *dev)
|
|
{
|
|
struct adc_info *info = dev->driver_data;
|
|
const struct adc_config *config = info->dev->config->config_info;
|
|
const struct adc_sequence *entry = info->ctx.sequence;
|
|
u32_t adc_base = config->reg_base;
|
|
u32_t ctrl, tmp_val, interval_us = 0U;
|
|
|
|
info->channel_id = find_lsb_set(info->channels) - 1;
|
|
|
|
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;
|
|
sys_out32(tmp_val, adc_base + ADC_SET);
|
|
|
|
if (entry->options) {
|
|
interval_us = entry->options->interval_us;
|
|
}
|
|
tmp_val = ((interval_us & ELEVEN_BITS_SET)
|
|
<< SEQ_DELAY_EVEN_POS);
|
|
tmp_val |= (info->channel_id & FIVE_BITS_SET);
|
|
|
|
sys_out32(tmp_val, adc_base + ADC_SEQ);
|
|
sys_out32(ctrl | ADC_SEQ_PTR_RST, adc_base + ADC_CTRL);
|
|
|
|
sys_out32(START_ADC_SEQ, adc_base + ADC_CTRL);
|
|
}
|
|
|
|
static void adc_context_start_sampling(struct adc_context *ctx)
|
|
{
|
|
struct adc_info *info = CONTAINER_OF(ctx, struct adc_info, ctx);
|
|
|
|
info->channels = ctx->sequence->channels;
|
|
|
|
adc_dw_start_conversion(info->dev);
|
|
}
|
|
|
|
static void adc_context_update_buffer_pointer(struct adc_context *ctx,
|
|
bool repeat)
|
|
{
|
|
struct adc_info *info = CONTAINER_OF(ctx, struct adc_info, ctx);
|
|
const struct adc_sequence *entry = ctx->sequence;
|
|
|
|
if (repeat) {
|
|
info->buffer = (u16_t *)entry->buffer;
|
|
}
|
|
}
|
|
|
|
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->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);
|
|
val &= ~(1 << INPUT_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();
|
|
|
|
int_unmask(config->reg_irq_mask);
|
|
int_unmask(config->reg_err_mask);
|
|
|
|
info->dev = dev;
|
|
|
|
adc_dw_enable(dev);
|
|
adc_context_unlock_unconditionally(&info->ctx);
|
|
return 0;
|
|
}
|
|
|
|
static void adc_dw_rx_isr(void *arg)
|
|
{
|
|
struct device *dev = (struct device *)arg;
|
|
struct adc_info *info = dev->driver_data;
|
|
const struct adc_config *config = dev->config->config_info;
|
|
u32_t adc_base = config->reg_base;
|
|
u32_t reg_val;
|
|
|
|
reg_val = sys_in32(adc_base + ADC_SET);
|
|
sys_out32(reg_val|ADC_POP_SAMPLE, adc_base + ADC_SET);
|
|
*info->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);
|
|
|
|
/*Clear data A register*/
|
|
reg_val = sys_in32(adc_base + ADC_CTRL);
|
|
sys_out32(reg_val | ADC_CLR_DATA_A, adc_base + ADC_CTRL);
|
|
|
|
info->state = ADC_STATE_IDLE;
|
|
info->channels &= ~BIT(info->channel_id);
|
|
|
|
if (info->channels) {
|
|
adc_dw_start_conversion(dev);
|
|
} else {
|
|
adc_context_on_sampling_done(&info->ctx, dev);
|
|
}
|
|
}
|
|
|
|
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;
|
|
adc_context_on_sampling_done(&info->ctx, dev);
|
|
}
|
|
|
|
static const struct adc_driver_api api_funcs = {
|
|
.channel_setup = adc_dw_channel_setup,
|
|
.read = adc_dw_read,
|
|
#ifdef CONFIG_ADC_ASYNC
|
|
.read_async = adc_dw_read_async,
|
|
#endif
|
|
};
|
|
|
|
const static struct adc_config adc_config_dev = {
|
|
.reg_base = DT_ADC_0_BASE_ADDRESS,
|
|
.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
|
|
.seq_mode = 0,
|
|
|
|
#ifdef CONFIG_ADC_DW_RISING_EDGE
|
|
.capture_mode = 0,
|
|
#elif CONFIG_ADC_DW_FALLING_EDGE
|
|
.capture_mode = 1,
|
|
#endif
|
|
.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, DT_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(DT_ADC_0_IRQ, DT_ADC_0_IRQ_PRI, adc_dw_rx_isr,
|
|
DEVICE_GET(adc_dw), 0);
|
|
irq_enable(DT_ADC_0_IRQ);
|
|
|
|
IRQ_CONNECT(DT_ADC_IRQ_ERR, DT_ADC_0_IRQ_PRI,
|
|
adc_dw_err_isr, DEVICE_GET(adc_dw), 0);
|
|
irq_enable(DT_ADC_IRQ_ERR);
|
|
}
|