zephyr/drivers/adc/adc_intel_quark_d2000.c

492 lines
12 KiB
C

/*
* Copyright (c) 2018 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 LOG_LEVEL CONFIG_ADC_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(adc_intel_quark_d2000);
#define ADC_CONTEXT_USES_KERNEL_TIMER
#include "adc_context.h"
#define MAX_CHANNELS 18
#define REG_CCU_PERIPH_CLK_GATE_CTL (SCSS_REGISTER_BASE + 0x18)
#define CLK_PERIPH_CLK BIT(1)
#define CLK_PERIPH_ADC BIT(22)
#define CLK_PERIPH_ADC_REGISTER BIT(23)
#define REG_CCU_PERIPH_CLK_DIV_CTL0 (SCSS_REGISTER_BASE + 0x1C)
#define CLK_DIV_ADC_POS 16
#define CLK_DIV_ADC_MASK (0x3FF << CLK_DIV_ADC_POS)
#define REG_INT_ADC_PWR_MASK (SCSS_REGISTER_BASE + 0x4CC)
#define REG_INT_ADC_CALIB_MASK (SCSS_REGISTER_BASE + 0x4D0)
#define ADC_DIV_MAX (1023)
#define ADC_DELAY_MAX (0x1FFF)
#define ADC_CAL_MAX (0x3F)
#define ADC_FIFO_LEN (32)
#define ADC_FIFO_CLEAR (0xFFFFFFFF)
/* ADC sequence table */
#define ADC_CAL_SEQ_TABLE_DEFAULT (0x80808080)
/* ADC command */
#define ADC_CMD_SW_OFFSET (24)
#define ADC_CMD_SW_MASK (0xFF000000)
#define ADC_CMD_CAL_DATA_OFFSET (16)
#define ADC_CMD_RESOLUTION_OFFSET (14)
#define ADC_CMD_RESOLUTION_MASK (0xC000)
#define ADC_CMD_NS_OFFSET (4)
#define ADC_CMD_NS_MASK (0x1F0)
#define ADC_CMD_IE_OFFSET (3)
#define ADC_CMD_IE BIT(3)
#define ADC_CMD_START_SINGLE (0)
#define ADC_CMD_START_CONT (1)
#define ADC_CMD_RESET_CAL (2)
#define ADC_CMD_START_CAL (3)
#define ADC_CMD_LOAD_CAL (4)
#define ADC_CMD_STOP_CONT (5)
/* Interrupt enable */
#define ADC_INTR_ENABLE_CC BIT(0)
#define ADC_INTR_ENABLE_FO BIT(1)
#define ADC_INTR_ENABLE_CONT_CC BIT(2)
/* Interrupt status */
#define ADC_INTR_STATUS_CC BIT(0)
#define ADC_INTR_STATUS_FO BIT(1)
#define ADC_INTR_STATUS_CONT_CC BIT(2)
/* Operating mode */
#define ADC_OP_MODE_IE BIT(27)
#define ADC_OP_MODE_DELAY_OFFSET (0x3)
#define ADC_OP_MODE_DELAY_MASK (0xFFF8)
#define ADC_OP_MODE_OM_MASK (0x7)
#define FIFO_INTR_THRESHOLD (ADC_FIFO_LEN / 2)
enum {
ADC_MODE_DEEP_PWR_DOWN, /**< Deep power down mode. */
ADC_MODE_PWR_DOWN, /**< Power down mode. */
ADC_MODE_STDBY, /**< Standby mode. */
ADC_MODE_NORM_CAL, /**< Normal mode, with calibration. */
ADC_MODE_NORM_NO_CAL /**< Normal mode, no calibration. */
};
/** ADC register map */
typedef struct {
u32_t seq[8]; /**< ADC Channel Sequence Table Entry 0 */
u32_t cmd; /**< ADC Command Register */
u32_t intr_status; /**< ADC Interrupt Status Register */
u32_t intr_enable; /**< ADC Interrupt Enable Register */
u32_t sample; /**< ADC Sample Register */
u32_t calibration; /**< ADC Calibration Data Register */
u32_t fifo_count; /**< ADC FIFO Count Register */
u32_t op_mode; /**< ADC Operating Mode Register */
} adc_reg_t;
struct adc_quark_d2000_config {
adc_reg_t *reg_base;
void (*config_func)(struct device *dev);
};
struct adc_quark_d2000_info {
struct device *dev;
struct adc_context ctx;
u16_t *buffer;
u32_t active_channels;
u32_t channels;
u8_t channel_id;
/** Sequence entries array */
const struct adc_sequence *entries;
/** Resolution value (mapped) */
u8_t resolution;
/** Sampling window */
u8_t sample_window;
};
static struct adc_quark_d2000_info adc_quark_d2000_data_0 = {
ADC_CONTEXT_INIT_TIMER(adc_quark_d2000_data_0, ctx),
ADC_CONTEXT_INIT_LOCK(adc_quark_d2000_data_0, ctx),
ADC_CONTEXT_INIT_SYNC(adc_quark_d2000_data_0, ctx),
};
static void adc_quark_d2000_set_mode(struct device *dev, int mode)
{
const struct adc_quark_d2000_config *config = dev->config->config_info;
volatile adc_reg_t *adc_regs = config->reg_base;
/* Set mode and wait for change */
adc_regs->op_mode = mode;
while ((adc_regs->op_mode & ADC_OP_MODE_OM_MASK) != mode)
;
/* Perform a dummy conversion if going into normal mode */
if (mode >= ADC_MODE_NORM_CAL) {
/* setup sequence table */
adc_regs->seq[0] = ADC_CAL_SEQ_TABLE_DEFAULT;
/* clear command complete interrupt */
adc_regs->intr_status = ADC_INTR_STATUS_CC;
/* run dummy conversion and wait for completion */
adc_regs->cmd = (ADC_CMD_IE | ADC_CMD_START_SINGLE);
while (!(adc_regs->intr_status & ADC_INTR_STATUS_CC))
;
/* flush FIFO */
adc_regs->sample = ADC_FIFO_CLEAR;
/* clear command complete interrupt (again) */
adc_regs->intr_status = ADC_INTR_STATUS_CC;
}
}
#ifdef CONFIG_ADC_INTEL_QUARK_D2000_CALIBRATION
static void adc_quark_d2000_goto_normal_mode(struct device *dev)
{
const struct adc_quark_d2000_config *config = dev->config->config_info;
volatile adc_reg_t *adc_regs = config->reg_base;
/* Set controller mode*/
adc_quark_d2000_set_mode(dev, ADC_MODE_NORM_CAL);
/* Perform calibration */
/* clear command complete interrupt */
adc_regs->intr_status = ADC_INTR_STATUS_CC;
/* start the calibration and wait for completion */
adc_regs->cmd = (ADC_CMD_IE | ADC_CMD_START_CAL);
while (!(adc_regs->intr_status & ADC_INTR_STATUS_CC))
;
/* clear command complete interrupt */
adc_regs->intr_status = ADC_INTR_STATUS_CC;
}
#else
static void adc_quark_d2000_goto_normal_mode(struct device *dev)
{
adc_quark_d2000_set_mode(dev, ADC_MODE_NORM_NO_CAL);
}
#endif
static void adc_quark_d2000_enable(struct device *dev)
{
adc_quark_d2000_goto_normal_mode(dev);
}
static int adc_quark_d2000_channel_setup(struct device *dev,
const struct adc_channel_cfg *channel_cfg)
{
struct adc_quark_d2000_info *info = dev->driver_data;
u8_t channel_id = channel_cfg->channel_id;
if (channel_id > MAX_CHANNELS) {
LOG_ERR("Channel %d is not valid", channel_id);
return -EINVAL;
}
if (channel_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) {
LOG_ERR("Invalid channel acquisition time");
return -EINVAL;
}
if (channel_cfg->differential) {
LOG_ERR("Differential channels are not supported");
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;
}
info->active_channels |= 1 << channel_id;
return 0;
}
static int adc_quark_d2000_read_request(struct device *dev,
const struct adc_sequence *seq_tbl)
{
struct adc_quark_d2000_info *info = dev->driver_data;
int error;
u32_t utmp, num_channels, interval = 0U;
info->channels = seq_tbl->channels & info->active_channels;
if (seq_tbl->channels != info->channels) {
return -EINVAL;
}
/* make sure resolution is valid */
switch (seq_tbl->resolution) {
case 6:
case 8:
case 10:
case 12:
info->resolution = (seq_tbl->resolution / 2) - 3;
/* sampling window is (resolution + 2) cycles */
info->sample_window = seq_tbl->resolution + 2;
break;
default:
return -EINVAL;
}
/*
* Make sure the requested interval is longer than the time
* needed to do one conversion.
*/
if (seq_tbl->options &&
(seq_tbl->options->interval_us > 0)) {
/*
* System clock is 32MHz, which means 1us == 32 cycles
* if divider is 1.
*/
interval = seq_tbl->options->interval_us * 32 /
CONFIG_ADC_INTEL_QUARK_D2000_CLOCK_RATIO;
if (interval < info->sample_window) {
return -EINVAL;
}
}
info->entries = seq_tbl;
info->buffer = (u16_t *)seq_tbl->buffer;
/* check if buffer has enough size */
utmp = info->channels;
num_channels = 0U;
while (utmp) {
if (utmp & BIT(0)) {
num_channels++;
}
utmp >>= 1;
}
utmp = num_channels * sizeof(u16_t);
if (seq_tbl->options) {
utmp *= (1 + seq_tbl->options->extra_samplings);
}
if (utmp > seq_tbl->buffer_size) {
return -ENOMEM;
}
adc_context_start_read(&info->ctx, seq_tbl);
error = adc_context_wait_for_completion(&info->ctx);
return error;
}
static int adc_quark_d2000_read(struct device *dev,
const struct adc_sequence *sequence)
{
struct adc_quark_d2000_info *info = dev->driver_data;
int error;
adc_context_lock(&info->ctx, false, NULL);
error = adc_quark_d2000_read_request(dev, sequence);
adc_context_release(&info->ctx, error);
return error;
}
#ifdef CONFIG_ADC_ASYNC
static int adc_quark_d2000_read_async(struct device *dev,
const struct adc_sequence *sequence,
struct k_poll_signal *async)
{
struct adc_quark_d2000_info *info = dev->driver_data;
int error;
adc_context_lock(&info->ctx, true, async);
error = adc_quark_d2000_read_request(dev, sequence);
adc_context_release(&info->ctx, error);
return error;
}
#endif
static void adc_quark_d2000_start_conversion(struct device *dev)
{
struct adc_quark_d2000_info *info = dev->driver_data;
const struct adc_quark_d2000_config *config =
info->dev->config->config_info;
volatile adc_reg_t *adc_regs = config->reg_base;
u32_t val;
info->channel_id = find_lsb_set(info->channels) - 1;
/* flush the FIFO */
adc_regs->sample = ADC_FIFO_CLEAR;
/* setup the sequence table */
adc_regs->seq[0] = info->channel_id | BIT(7);
/* clear pending interrupts */
adc_regs->intr_status = ADC_INTR_STATUS_CC;
/* enable command completion interrupts */
adc_regs->intr_enable = ADC_INTR_ENABLE_CC;
/* issue command to start conversion */
val = info->sample_window << ADC_CMD_SW_OFFSET;
val |= info->resolution << ADC_CMD_RESOLUTION_OFFSET;
val |= (ADC_CMD_IE | ADC_CMD_START_SINGLE);
adc_regs->cmd = val;
}
static void adc_context_start_sampling(struct adc_context *ctx)
{
struct adc_quark_d2000_info *info =
CONTAINER_OF(ctx, struct adc_quark_d2000_info, ctx);
info->channels = ctx->sequence->channels;
adc_quark_d2000_start_conversion(info->dev);
}
static void adc_context_update_buffer_pointer(struct adc_context *ctx,
bool repeat)
{
struct adc_quark_d2000_info *info =
CONTAINER_OF(ctx, struct adc_quark_d2000_info, ctx);
const struct adc_sequence *entry = ctx->sequence;
if (repeat) {
info->buffer = (u16_t *)entry->buffer;
}
}
static int adc_quark_d2000_init(struct device *dev)
{
const struct adc_quark_d2000_config *config =
dev->config->config_info;
struct adc_quark_d2000_info *info = dev->driver_data;
u32_t val;
/* Enable the ADC and set the clock divisor */
val = sys_read32(REG_CCU_PERIPH_CLK_GATE_CTL);
val |= (CLK_PERIPH_CLK | CLK_PERIPH_ADC | CLK_PERIPH_ADC_REGISTER);
sys_write32(val, REG_CCU_PERIPH_CLK_GATE_CTL);
/* ADC clock divider */
val = sys_read32(REG_CCU_PERIPH_CLK_DIV_CTL0);
val &= ~CLK_DIV_ADC_MASK;
val |= ((CONFIG_ADC_INTEL_QUARK_D2000_CLOCK_RATIO - 1)
<< CLK_DIV_ADC_POS) & CLK_DIV_ADC_MASK;
sys_write32(val, REG_CCU_PERIPH_CLK_DIV_CTL0);
/* Clear host interrupt mask */
val = sys_read32(REG_INT_ADC_PWR_MASK);
val &= ~1;
sys_write32(val, REG_INT_ADC_PWR_MASK);
val = sys_read32(REG_INT_ADC_CALIB_MASK);
val &= ~1;
sys_write32(val, REG_INT_ADC_CALIB_MASK);
config->config_func(dev);
info->dev = dev;
adc_quark_d2000_enable(dev);
adc_context_unlock_unconditionally(&info->ctx);
return 0;
}
static void adc_quark_d2000_isr(void *arg)
{
struct device *dev = (struct device *)arg;
const struct adc_quark_d2000_config *config = dev->config->config_info;
struct adc_quark_d2000_info *info = dev->driver_data;
volatile adc_reg_t *adc_regs = config->reg_base;
u32_t intr_status;
u32_t to_read, val;
intr_status = adc_regs->intr_status;
/* single conversion command completion */
if (intr_status & ADC_INTR_STATUS_CC) {
adc_regs->intr_status = ADC_INTR_STATUS_CC;
to_read = adc_regs->fifo_count;
while (to_read--) {
/* read from FIFO */
val = adc_regs->sample;
/* sample is always 12-bit, so need to shift */
val = val >> (2 * (3 - info->resolution));
*info->buffer++ = val;
}
}
/* setup for next conversion if needed */
info->channels &= ~BIT(info->channel_id);
if (info->channels) {
adc_quark_d2000_start_conversion(dev);
} else {
adc_context_on_sampling_done(&info->ctx, dev);
}
}
static const struct adc_driver_api adc_quark_d2000_driver_api = {
.channel_setup = adc_quark_d2000_channel_setup,
.read = adc_quark_d2000_read,
#ifdef CONFIG_ADC_ASYNC
.read_async = adc_quark_d2000_read_async,
#endif
};
#if CONFIG_ADC_0
static void adc_quark_d2000_config_func_0(struct device *dev);
static const struct adc_quark_d2000_config adc_quark_d2000_config_0 = {
.reg_base = (adc_reg_t *)DT_ADC_0_BASE_ADDRESS,
.config_func = adc_quark_d2000_config_func_0,
};
DEVICE_AND_API_INIT(adc_quark_d2000_0, DT_ADC_0_NAME,
&adc_quark_d2000_init, &adc_quark_d2000_data_0,
&adc_quark_d2000_config_0, POST_KERNEL,
CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&adc_quark_d2000_driver_api);
static void adc_quark_d2000_config_func_0(struct device *dev)
{
IRQ_CONNECT(DT_ADC_0_IRQ, 0,
adc_quark_d2000_isr,
DEVICE_GET(adc_quark_d2000_0),
DT_ADC_0_IRQ_FLAGS);
irq_enable(DT_ADC_0_IRQ);
}
#endif /* CONFIG_ADC_0 */