| /* |
| * 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 / 2U) - 3; |
| |
| /* sampling window is (resolution + 2) cycles */ |
| info->sample_window = seq_tbl->resolution + 2U; |
| 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 * 32U / |
| 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 */ |