|  | /* | 
|  | * 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; | 
|  |  | 
|  | /** Sequence size */ | 
|  | u8_t seq_size; | 
|  |  | 
|  | /** Resolution value (mapped) */ | 
|  | u8_t resolution; | 
|  | }; | 
|  |  | 
|  | 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; | 
|  |  | 
|  | /* 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; | 
|  | } | 
|  |  | 
|  | /* make sure resolution is valid */ | 
|  | switch (seq_tbl->resolution) { | 
|  | case 6: | 
|  | case 8: | 
|  | case 10: | 
|  | case 12: | 
|  | info->resolution = (seq_tbl->resolution / 2) - 3; | 
|  | break; | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | if (info->seq_size > ADC_FIFO_LEN) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* check if buffer has enough size */ | 
|  | utmp = info->channels; | 
|  | num_channels = 0U; | 
|  | while (utmp) { | 
|  | if (utmp & BIT(0)) { | 
|  | num_channels++; | 
|  | } | 
|  | utmp >>= 1; | 
|  | } | 
|  | utmp = info->seq_size * num_channels * sizeof(u16_t); | 
|  | if (utmp > seq_tbl->buffer_size) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | adc_context_start_read(&info->ctx, seq_tbl); | 
|  | error = adc_context_wait_for_completion(&info->ctx); | 
|  | adc_context_release(&info->ctx, error); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int adc_quark_d2000_read(struct device *dev, | 
|  | const struct adc_sequence *sequence) | 
|  | { | 
|  | struct adc_quark_d2000_info *info = dev->driver_data; | 
|  |  | 
|  | adc_context_lock(&info->ctx, false, NULL); | 
|  |  | 
|  | return adc_quark_d2000_read_request(dev, sequence); | 
|  | } | 
|  |  | 
|  | #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; | 
|  |  | 
|  | adc_context_lock(&info->ctx, true, async); | 
|  |  | 
|  | return adc_quark_d2000_read_request(dev, sequence); | 
|  | } | 
|  | #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; | 
|  | const struct adc_sequence *entry = info->ctx.sequence; | 
|  | volatile adc_reg_t *adc_regs = config->reg_base; | 
|  | u32_t i, val, interval_us = 0U; | 
|  | u32_t idx = 0U, offset = 0U; | 
|  |  | 
|  | info->channel_id = find_lsb_set(info->channels) - 1; | 
|  |  | 
|  | if (entry->options) { | 
|  | interval_us = entry->options->interval_us; | 
|  | } | 
|  |  | 
|  | /* flush the FIFO */ | 
|  | adc_regs->sample = ADC_FIFO_CLEAR; | 
|  |  | 
|  | /* setup the sequence table */ | 
|  | for (i = 0U; i < info->seq_size; i++) { | 
|  | idx = i / 4; | 
|  | offset = (i % 4) * 8; | 
|  |  | 
|  | val = adc_regs->seq[idx]; | 
|  |  | 
|  | /* clear last of sequence bit */ | 
|  | val &= ~(1 << (offset + 7)); | 
|  |  | 
|  | /* set channel number */ | 
|  | val |= (info->channel_id << offset); | 
|  |  | 
|  | adc_regs->seq[idx] = val; | 
|  | } | 
|  |  | 
|  | /* set last of sequence bit */ | 
|  | if (info->seq_size > 1) { | 
|  | val = adc_regs->seq[idx]; | 
|  | val |= (1 << (offset + 7)); | 
|  | adc_regs->seq[idx] = val; | 
|  | } | 
|  |  | 
|  | /* 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 = interval_us << 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 */ |