| /**************************************************************************** |
| * arch/arm/src/samv7/sam_afec.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <stdint.h> |
| #include <inttypes.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <arch/board/board.h> |
| #include <arch/chip/sam_afec.h> |
| #include <nuttx/irq.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/wqueue.h> |
| #include <nuttx/analog/adc.h> |
| #include <nuttx/analog/ioctl.h> |
| |
| #include "arm_internal.h" |
| #include "hardware/sam_matrix.h" |
| #include "hardware/sam_pinmap.h" |
| #include "hardware/sam_pio.h" |
| #include "sam_periphclks.h" |
| #include "sam_gpio.h" |
| #include "sam_tc.h" |
| #include "sam_afec.h" |
| #include "sam_xdmac.h" |
| |
| #ifdef CONFIG_ADC |
| |
| #if defined(CONFIG_SAMV7_AFEC0) || defined(CONFIG_SAMV7_AFEC1) |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #define ADC_MAX_CHANNELS 12 |
| #define AFEC0_MAX_PINS 11 |
| #define AFEC1_MAX_PINS 12 |
| |
| #ifdef CONFIG_SAMV7_AFEC_DMA |
| #define DMA_FLAGS (DMACH_FLAG_FIFOCFG_LARGEST | \ |
| DMACH_FLAG_PERIPHH2SEL | DMACH_FLAG_PERIPHISPERIPH | \ |
| DMACH_FLAG_PERIPHWIDTH_32BITS | DMACH_FLAG_PERIPHCHUNKSIZE_1 | \ |
| DMACH_FLAG_MEMPID_MAX | DMACH_FLAG_MEMAHB_AHB_IF0 | \ |
| DMACH_FLAG_PERIPHAHB_AHB_IF1 | DMACH_FLAG_MEMWIDTH_32BITS | \ |
| DMACH_FLAG_MEMINCREMENT | DMACH_FLAG_MEMCHUNKSIZE_1 | \ |
| DMACH_FLAG_MEMBURST_1) |
| #endif |
| |
| #if !defined(CONFIG_SAMV7_AFEC_DMA) |
| # undef CONFIG_SAMV7_AFEC_DMASAMPLES |
| # define CONFIG_SAMV7_AFEC_DMASAMPLES 1 |
| #elif !defined(CONFIG_SAMV7_AFEC_DMASAMPLES) |
| # error CONFIG_SAMV7_AFEC_DMASAMPLES must be defined |
| #elif CONFIG_SAMV7_AFEC_DMASAMPLES < 2 |
| # warning Values of CONFIG_SAMV7_AFEC_DMASAMPLES < 2 are inefficient |
| #endif |
| |
| #define SAMV7_AFEC_SAMPLES (CONFIG_SAMV7_AFEC_DMASAMPLES * ADC_MAX_CHANNELS) |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| enum samv7_afec_triggers |
| { |
| AFEC_TRIGGER_SW = 0, |
| AFEC_TRIGGER_TIMER, |
| AFEC_TRIGGER_PWM |
| }; |
| |
| struct samv7_dev_s |
| { |
| const struct adc_callback_s *cb; /* Upper driver callback */ |
| uint8_t intf; /* ADC number (i.e. ADC1, ADC2) */ |
| uint32_t base; /* ADC register base */ |
| uint8_t max_pins; |
| uint8_t initialized; /* ADC initialization counter */ |
| uint8_t resolution; /* ADC resolution (SAMV7_AFECn_RES) */ |
| uint8_t trigger; /* ADC trigger (software, timer...) */ |
| uint8_t timer_channel; /* Timer channel to trigger ADC */ |
| uint8_t event_line; /* PWM event line to trigger ADC */ |
| uint32_t frequency; /* Frequency of the timer */ |
| int irq; /* ADC IRQ number */ |
| int pid; /* ADC PID number */ |
| int nchannels; /* Number of configured channels */ |
| uint8_t chanlist[ADC_MAX_CHANNELS]; /* ADC channel list */ |
| uint8_t current; /* Current channel being converted */ |
| #ifdef CONFIG_SAMV7_AFEC_TIOATRIG |
| TC_HANDLE tc; /* Handle for the timer channel */ |
| #endif |
| |
| #ifdef CONFIG_SAMV7_AFEC_DMA |
| volatile bool odd; /* Odd buffer is in use */ |
| volatile bool ready; /* Worker has completed the last set of samples */ |
| volatile bool enabled; /* DMA data transfer is enabled */ |
| int nsamples; |
| DMA_HANDLE dma; /* Handle for DMA channel */ |
| struct work_s work; /* Supports the interrupt handling "bottom half" */ |
| |
| /* DMA sample data buffer */ |
| |
| uint32_t evenbuf[SAMV7_AFEC_SAMPLES]; |
| uint32_t oddbuf[SAMV7_AFEC_SAMPLES]; |
| #endif |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| static void afec_putreg(struct samv7_dev_s *priv, uint32_t offset, |
| uint32_t value); |
| static uint32_t afec_getreg(struct samv7_dev_s *priv, uint32_t offset); |
| |
| #ifdef CONFIG_SAMV7_AFEC_DMA |
| static void sam_afec_dmadone(void *arg); |
| static void sam_afec_dmacallback(DMA_HANDLE handle, void *arg, int result); |
| static int sam_afec_dmasetup(struct adc_dev_s *dev, uint8_t *buffer, |
| size_t buflen); |
| static void sam_afec_dmastart(struct adc_dev_s *dev); |
| #endif |
| |
| #ifdef CONFIG_SAMV7_AFEC_TIOATRIG |
| static int sam_afec_settimer(struct samv7_dev_s *priv, uint32_t frequency, |
| int channel); |
| static void sam_afec_freetimer(struct samv7_dev_s *priv); |
| #endif |
| |
| static int sam_afec_trigger(struct samv7_dev_s *priv); |
| |
| /* ADC methods */ |
| |
| static int afec_bind(struct adc_dev_s *dev, |
| const struct adc_callback_s *callback); |
| static void afec_reset(struct adc_dev_s *dev); |
| static int afec_setup(struct adc_dev_s *dev); |
| static void afec_shutdown(struct adc_dev_s *dev); |
| static void afec_rxint(struct adc_dev_s *dev, bool enable); |
| static int afec_ioctl(struct adc_dev_s *dev, |
| int cmd, unsigned long arg); |
| static int afec_interrupt(int irq, void *context, void *arg); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static const struct adc_ops_s g_adcops = |
| { |
| .ao_bind = afec_bind, |
| .ao_reset = afec_reset, |
| .ao_setup = afec_setup, |
| .ao_shutdown = afec_shutdown, |
| .ao_rxint = afec_rxint, |
| .ao_ioctl = afec_ioctl, |
| }; |
| |
| #ifdef CONFIG_SAMV7_AFEC0 |
| static struct samv7_dev_s g_adcpriv0 = |
| { |
| .irq = SAM_IRQ_AFEC0, |
| .pid = SAM_PID_AFEC0, |
| .intf = 0, |
| .max_pins = AFEC0_MAX_PINS, |
| .initialized = 0, |
| .resolution = CONFIG_SAMV7_AFEC0_RES, |
| #if defined (CONFIG_SAMV7_AFEC0_PWMTRIG) |
| .trigger = AFEC_TRIGGER_PWM, |
| .event_line = CONFIG_SAMV7_AFEC0_PWMEVENT, |
| #elif defined (CONFIG_SAMV7_AFEC0_TIOATRIG) |
| .trigger = AFEC_TRIGGER_TIMER, |
| .timer_channel = CONFIG_SAMV7_AFEC0_TIOACHAN, |
| .frequency = CONFIG_SAMV7_AFEC0_TIOAFREQ, |
| #else |
| .trigger = AFEC_TRIGGER_SW, |
| #endif |
| .base = SAM_AFEC0_BASE, |
| }; |
| |
| static struct adc_dev_s g_adcdev0 = |
| { |
| .ad_ops = &g_adcops, |
| .ad_priv = &g_adcpriv0, |
| }; |
| |
| gpio_pinset_t g_adcpinlist0[AFEC0_MAX_PINS] = |
| { |
| GPIO_AFE0_AD0, |
| GPIO_AFE0_AD1, |
| GPIO_AFE0_AD2, |
| GPIO_AFE0_AD3, |
| GPIO_AFE0_AD4, |
| GPIO_AFE0_AD5, |
| GPIO_AFE0_AD6, |
| GPIO_AFE0_AD7, |
| GPIO_AFE0_AD8, |
| GPIO_AFE0_AD9, |
| GPIO_AFE0_AD10, |
| }; |
| #endif |
| |
| #ifdef CONFIG_SAMV7_AFEC1 |
| static struct samv7_dev_s g_adcpriv1 = |
| { |
| .irq = SAM_IRQ_AFEC1, |
| .pid = SAM_PID_AFEC1, |
| .intf = 1, |
| .max_pins = AFEC1_MAX_PINS, |
| .initialized = 0, |
| .resolution = CONFIG_SAMV7_AFEC1_RES, |
| #if defined (CONFIG_SAMV7_AFEC1_PWMTRIG) |
| .trigger = AFEC_TRIGGER_PWM, |
| .event_line = CONFIG_SAMV7_AFEC0_PWMEVENT, |
| #elif defined (CONFIG_SAMV7_AFEC1_TIOATRIG) |
| .trigger = AFEC_TRIGGER_TIMER, |
| .timer_channel = CONFIG_SAMV7_AFEC1_TIOACHAN, |
| .frequency = CONFIG_SAMV7_AFEC1_TIOAFREQ, |
| #else |
| .trigger = AFEC_TRIGGER_SW, |
| #endif |
| .base = SAM_AFEC1_BASE, |
| }; |
| |
| static struct adc_dev_s g_adcdev1 = |
| { |
| .ad_ops = &g_adcops, |
| .ad_priv = &g_adcpriv1, |
| }; |
| |
| gpio_pinset_t g_adcpinlist1[AFEC1_MAX_PINS] = |
| { |
| GPIO_AFE1_AD0, |
| GPIO_AFE1_AD1, |
| GPIO_AFE1_AD2, |
| GPIO_AFE1_AD3, |
| GPIO_AFE1_AD4, |
| GPIO_AFE1_AD5, |
| GPIO_AFE1_AD6, |
| GPIO_AFE1_AD7, |
| GPIO_AFE1_AD8, |
| GPIO_AFE1_AD9, |
| GPIO_AFE1_AD10, |
| GPIO_AFE1_AD11 |
| }; |
| #endif |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| static void afec_putreg(struct samv7_dev_s *priv, uint32_t offset, |
| uint32_t value) |
| { |
| putreg32(value, priv->base + offset); |
| } |
| |
| static uint32_t afec_getreg(struct samv7_dev_s *priv, uint32_t offset) |
| { |
| return getreg32(priv->base + offset); |
| } |
| |
| /**************************************************************************** |
| * DMA Helpers |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: sam_afec_dmadone |
| * |
| * Description: |
| * This function executes on the worker thread. It is scheduled by |
| * sam_adc_dmacallback at the complete of each DMA sequenece. There is |
| * and interlock using ping-pong buffers and boolean values to prevent |
| * overrunning the worker thread: |
| * |
| * oddbuf[]/evenbuf[] - Ping pong buffers are used. The DMA collects |
| * data in one buffer while the worker thread processes data in the |
| * other. |
| * odd - If true, then DMA is active in the oddbuf[]; evenbuf[] holds |
| * completed DMA data. |
| * ready - Ping ponging is halted while ready is false; If data overrun |
| * occurs, then sample data will be lost on one sequence. The worker |
| * thread sets ready when it has completed processing the last sample |
| * data. |
| * |
| * Input Parameters: |
| * arg - The ADC private data structure cast to (void *) |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMV7_AFEC_DMA |
| static void sam_afec_dmadone(void *arg) |
| { |
| struct adc_dev_s *dev = (struct adc_dev_s *)arg; |
| struct samv7_dev_s *priv = (struct samv7_dev_s *)dev->ad_priv; |
| uint32_t *buffer; |
| uint32_t *next; |
| uint32_t sample; |
| int chan; |
| int i; |
| |
| ainfo("ready=%d enabled=%d\n", priv->enabled, priv->ready); |
| DEBUGASSERT(priv != NULL && !priv->ready); |
| |
| /* If the DMA transfer is not enabled, just ignore the data (and do not |
| * start the next DMA transfer). |
| */ |
| |
| if (priv->enabled) |
| { |
| /* Toggle to the next buffer. |
| * |
| * buffer - The buffer on which the DMA has just completed |
| * next - The buffer in which to start the next DMA |
| */ |
| |
| if (priv->odd) |
| { |
| buffer = priv->oddbuf; |
| next = priv->evenbuf; |
| priv->odd = false; |
| } |
| else |
| { |
| buffer = priv->evenbuf; |
| next = priv->oddbuf; |
| priv->odd = true; |
| } |
| |
| /* Restart the DMA conversion as quickly as possible using the next |
| * buffer. |
| */ |
| |
| sam_afec_dmasetup(dev, (uint8_t *)next, |
| priv->nsamples * sizeof(uint32_t)); |
| |
| /* Invalidate the DMA buffer so that we are guaranteed to reload the |
| * newly DMAed data from RAM. |
| */ |
| |
| up_invalidate_dcache((uintptr_t)buffer, |
| (uintptr_t)buffer + |
| priv->nsamples * sizeof(uint32_t)); |
| |
| /* Process each sample */ |
| |
| for (i = 0; i < priv->nsamples; i++, buffer++) |
| { |
| /* Get the sample and the channel number */ |
| |
| chan = (int)((*buffer & AFEC_LCDR_CHANB_MASK) >> |
| AFEC_LCDR_CHANB_SHIFT); |
| sample = ((*buffer & AFEC_LCDR_LDATA_MASK) >> |
| AFEC_LCDR_LDATA_SHIFT); |
| |
| /* Verify the upper-half driver has bound its callback functions */ |
| |
| if (priv->cb != NULL) |
| { |
| /* Give the sample data to the ADC upper half */ |
| |
| DEBUGASSERT(priv->cb->au_receive != NULL); |
| priv->cb->au_receive(dev, chan, sample); |
| } |
| } |
| } |
| |
| /* We are ready to handle the next sample sequence */ |
| |
| priv->ready = true; |
| } |
| |
| /**************************************************************************** |
| * Name: sam_afec_dmacallback |
| * |
| * Description: |
| * Called when one ADC DMA sequence completes. This function defers |
| * processing of the samples to sam_adc_dmadone which runs on the worker |
| * thread. |
| * |
| ****************************************************************************/ |
| |
| static void sam_afec_dmacallback(DMA_HANDLE handle, void *arg, int result) |
| { |
| struct adc_dev_s *dev = (struct adc_dev_s *)arg; |
| struct samv7_dev_s *priv = (struct samv7_dev_s *)dev->ad_priv; |
| int ret; |
| |
| ainfo("ready=%d enabled=%d\n", priv->enabled, priv->ready); |
| DEBUGASSERT(priv->ready); |
| |
| /* Check of the bottom half is keeping up with us. |
| * |
| * ready == false: Would mean that the worker thready has not ran since |
| * the last DMA callback. |
| * enabled == false: Means that the upper half has asked us nicely to stop |
| * transferring DMA data. |
| */ |
| |
| if (priv->ready && priv->enabled) |
| { |
| /* Verify that the worker is available */ |
| |
| DEBUGASSERT(priv->work.worker == NULL); |
| |
| /* Mark the work as busy and schedule the DMA done processing to |
| * occur on the worker thread. |
| */ |
| |
| priv->ready = false; |
| ret = work_queue(HPWORK, &priv->work, sam_afec_dmadone, dev, 0); |
| if (ret != 0) |
| { |
| aerr("ERROR: Failed to queue work: %d\n", ret); |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: sam_afec_dmasetup |
| * |
| * Description: |
| * Setup to perform a read DMA. If the processor supports a data cache, |
| * then this method will also make sure that the contents of the DMA memory |
| * and the data cache are coherent. For read transfers this may mean |
| * invalidating the data cache. |
| * |
| * Input Parameters: |
| * priv - An instance of the ADC device interface |
| * buffer - The memory to DMA from |
| * buflen - The size of the DMA transfer in bytes |
| * |
| * Returned Value: |
| * OK on success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| static int sam_afec_dmasetup(struct adc_dev_s *dev, uint8_t *buffer, |
| size_t buflen) |
| { |
| struct samv7_dev_s *priv = (struct samv7_dev_s *)dev->ad_priv; |
| uint32_t paddr; |
| uint32_t maddr; |
| |
| ainfo("buffer=%p buflen=%d\n", buffer, (int)buflen); |
| DEBUGASSERT(priv != NULL && buffer != NULL && buflen > 0); |
| DEBUGASSERT(((uint32_t)buffer & 3) == 0); |
| |
| /* Physical address of the ADC LCDR register and of the buffer location in |
| * RAM. |
| */ |
| |
| paddr = priv->base + SAM_AFEC_LCDR_OFFSET; |
| maddr = (uintptr_t)buffer; |
| |
| /* Configure the RX DMA */ |
| |
| sam_dmarxsetup(priv->dma, paddr, maddr, buflen); |
| |
| /* Start the DMA */ |
| |
| sam_dmastart(priv->dma, sam_afec_dmacallback, dev); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: sam_afec_dmastart |
| * |
| * Description: |
| * Initiate DMA sampling. |
| * |
| ****************************************************************************/ |
| |
| static void sam_afec_dmastart(struct adc_dev_s *dev) |
| { |
| struct samv7_dev_s *priv = (struct samv7_dev_s *)dev->ad_priv; |
| |
| /* Make sure that the worker is available and that DMA is not disabled */ |
| |
| if (priv->ready && priv->enabled) |
| { |
| priv->odd = false; /* Start with the even buffer */ |
| sam_afec_dmasetup(dev, (uint8_t *)priv->evenbuf, |
| priv->nsamples * sizeof(uint32_t)); |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: sam_adc_settimer |
| * |
| * Description: |
| * Configure a timer to trigger the sampling periodically |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMV7_AFEC_TIOATRIG |
| static int sam_afec_settimer(struct samv7_dev_s *priv, uint32_t frequency, |
| int channel) |
| { |
| uint32_t div; |
| uint32_t tcclks; |
| uint32_t actual; |
| uint32_t mode; |
| uint32_t fdiv; |
| uint32_t regval; |
| int ret; |
| |
| ainfo("frequency=%ld channel=%d\n", (long)frequency, channel); |
| DEBUGASSERT(priv && frequency > 0); |
| |
| /* Configure TC for a 1Hz frequency and trigger on RC compare. */ |
| |
| ret = sam_tc_clockselect(frequency, &tcclks, &actual); |
| if (ret < 0) |
| { |
| aerr("ERROR: sam_tc_divisor failed: %d\n", ret); |
| return ret; |
| } |
| |
| div = BOARD_MCK_FREQUENCY / actual; |
| |
| /* Set the timer/counter waveform mode the clock input selected by |
| * sam_tc_clockselect() |
| */ |
| |
| mode = ((tcclks << TC_CMR_TCCLKS_SHIFT) | /* Use selected TCCLKS value */ |
| TC_CMR_WAVSEL_UPRC | /* UP mode w/ trigger on RC Compare */ |
| TC_CMR_WAVE | /* Wave mode */ |
| TC_CMR_ACPA_CLEAR | /* RA Compare Effect on TIOA: Clear */ |
| TC_CMR_ACPC_SET); /* RC effect on TIOA: Set */ |
| |
| /* Now allocate and configure the channel */ |
| |
| priv->tc = sam_tc_allocate(channel, mode); |
| if (!priv->tc) |
| { |
| aerr("ERROR: Failed to allocate channel %d mode %08" PRIx32 "\n", |
| channel, mode); |
| return -EINVAL; |
| } |
| |
| /* The divider returned by sam_tc_clockselect() is the reload value |
| * that will achieve a 1Hz rate. We need to multiply this to get the |
| * desired frequency. sam_tc_divisor() should have already assure |
| * that we can do this without overflowing a 32-bit unsigned integer. |
| */ |
| |
| fdiv = div * frequency; |
| DEBUGASSERT(div > 0 && div <= fdiv); /* Will check for integer overflow */ |
| |
| /* Calculate the actual counter value from this divider and the tc input |
| * frequency. |
| */ |
| |
| regval = BOARD_MCK_FREQUENCY / fdiv; |
| |
| /* Set up TC_RA and TC_RC. The frequency is determined by RA and RC: |
| * TIOA is cleared on RA match; TIOA is set on RC match. |
| */ |
| |
| sam_tc_setregister(priv->tc, TC_REGA, regval >> 1); |
| sam_tc_setregister(priv->tc, TC_REGC, regval); |
| |
| /* And start the timer */ |
| |
| sam_tc_start(priv->tc); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: sam_afec_freetimer |
| * |
| * Description: |
| * Configure a timer to trigger the sampling periodically |
| * |
| ****************************************************************************/ |
| |
| static void sam_afec_freetimer(struct samv7_dev_s *priv) |
| { |
| /* Is a timer allocated? */ |
| |
| ainfo("tc=%p\n", priv->tc); |
| |
| if (priv->tc) |
| { |
| /* Yes.. stop it and free it */ |
| |
| sam_tc_stop(priv->tc); |
| sam_tc_free(priv->tc); |
| priv->tc = NULL; |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: sam_afec_trigger |
| * |
| * Description: |
| * Configure trigger mode and start conversion. |
| * |
| ****************************************************************************/ |
| |
| static int sam_afec_trigger(struct samv7_dev_s *priv) |
| { |
| uint32_t regval; |
| int ret = OK; |
| |
| #ifdef CONFIG_SAMV7_AFEC_SWTRIG |
| if (priv->trigger == AFEC_TRIGGER_SW) |
| { |
| ainfo("Setup software trigger\n"); |
| |
| /* Configure the software trigger */ |
| |
| regval = afec_getreg(priv, SAM_AFEC_MR_OFFSET); |
| regval &= ~AFEC_MR_TRGSEL_MASK; |
| afec_putreg(priv, SAM_AFEC_MR_OFFSET, regval); |
| } |
| |
| #endif |
| #ifdef CONFIG_SAMV7_AFEC_TIOATRIG |
| if (priv->trigger == AFEC_TRIGGER_TIMER) |
| { |
| ainfo("Setup timer/counter trigger\n"); |
| |
| /* Start the timer */ |
| |
| ret = sam_afec_settimer(priv, priv->frequency, priv->timer_channel); |
| if (ret < 0) |
| { |
| aerr("ERROR: sam_afec_settimer failed: %d\n", ret); |
| return ret; |
| } |
| |
| /* Set trigger for AFECn driver |
| * |
| * Note: AFEC_MR register still needs to select corresponding channels |
| * with 1, 2, 3 values (see TRGSEL bitfield description) even if |
| * channels 3, 4 and 5 are selected for AFEC1 |
| */ |
| |
| /* Set trigger for AFECn driver */ |
| |
| regval = afec_getreg(priv, SAM_AFEC_MR_OFFSET); |
| regval &= ~AFEC_MR_TRGSEL_MASK; |
| |
| regval |= (((priv->timer_channel % 3) + 1) << AFEC_MR_TRGSEL_SHIFT) | |
| AFEC_MR_TRGEN; |
| |
| afec_putreg(priv, SAM_AFEC_MR_OFFSET, regval); |
| } |
| |
| #endif |
| #ifdef CONFIG_SAMV7_AFEC_PWMTRIG |
| if (priv->trigger == AFEC_TRIGGER_PWM) |
| { |
| regval = afec_getreg(priv, SAM_AFEC_MR_OFFSET); |
| regval &= ~AFEC_MR_TRGSEL_MASK; |
| |
| if (priv->event_line == 1) |
| { |
| /* PWM Event Line 1 is used for AFEC triggering */ |
| |
| regval |= AFEC_MR_TRGSEL_PWM1; |
| } |
| else |
| { |
| /* PWM Event Line 0 is used for AFEC triggering */ |
| |
| regval |= AFEC_MR_TRGSEL_PWM0; |
| } |
| |
| regval |= AFEC_MR_TRGEN; |
| |
| afec_putreg(priv, SAM_AFEC_MR_OFFSET, regval); |
| } |
| #endif |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: afec_bind |
| * |
| * Description: |
| * Bind the upper-half driver callbacks to the lower-half implementation. |
| * This must be called early in order to receive ADC event notifications. |
| * |
| ****************************************************************************/ |
| |
| static int afec_bind(struct adc_dev_s *dev, |
| const struct adc_callback_s *callback) |
| { |
| struct samv7_dev_s *priv = (struct samv7_dev_s *)dev->ad_priv; |
| |
| DEBUGASSERT(priv != NULL); |
| priv->cb = callback; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: afec_reset |
| * |
| * Description: |
| * Reset the ADC device. Called early to initialize the hardware. This |
| * is called, before afec_setup() and on error conditions. |
| * |
| ****************************************************************************/ |
| |
| static void afec_reset(struct adc_dev_s *dev) |
| { |
| struct samv7_dev_s *priv = (struct samv7_dev_s *)dev->ad_priv; |
| irqstate_t flags; |
| |
| flags = enter_critical_section(); |
| |
| /* Do nothing if ADC instance is currently in use */ |
| |
| if (priv->initialized > 0) |
| { |
| goto exit_leave_critical; |
| } |
| |
| /* Stop any ongoing DMA */ |
| |
| #ifdef CONFIG_SAMV7_AFEC_DMA |
| if (priv->dma) |
| { |
| sam_dmastop(priv->dma); |
| } |
| #endif |
| |
| #ifdef CONFIG_SAMV7_AFEC_TIOATRIG |
| if (priv->trigger == AFEC_TRIGGER_TIMER) |
| { |
| sam_afec_freetimer(priv); |
| } |
| #endif |
| |
| /* Configure clock gating */ |
| |
| switch (priv->intf) |
| { |
| #ifdef CONFIG_SAMV7_AFEC0 |
| case 0: |
| sam_afec0_enableclk(); |
| break; |
| #endif |
| #ifdef CONFIG_SAMV7_AFEC1 |
| case 1: |
| sam_afec1_enableclk(); |
| break; |
| #endif |
| default: |
| aerr("ERROR: Tried to reset non-existing ADC: %d\n", priv->intf); |
| goto exit_leave_critical; |
| } |
| |
| leave_critical_section(flags); |
| |
| /* Software reset */ |
| |
| afec_putreg(priv, SAM_AFEC_CR_OFFSET, AFEC_CR_SWRST); |
| |
| /* Configure Mode Register */ |
| |
| uint32_t afec_mr = AFEC_MR_STARTUP_64 | AFEC_MR_PRESCAL(2) | AFEC_MR_ONE; |
| afec_putreg(priv, SAM_AFEC_MR_OFFSET, afec_mr); |
| |
| /* Configure Extended Mode register */ |
| |
| uint32_t afec_emr = AFEC_EMR_TAG | AFEC_EMR_STM | |
| AFEC_EMR_RES(priv->resolution); |
| afec_putreg(priv, SAM_AFEC_EMR_OFFSET, afec_emr); |
| |
| /* Configure Analog Control Register */ |
| |
| uint32_t afec_acr = AFEC_ACR_PGA0EN | AFEC_ACR_PGA1EN | AFEC_ACR_IBCTL(2); |
| afec_putreg(priv, SAM_AFEC_ACR_OFFSET, afec_acr); |
| |
| /* Pad configuration */ |
| |
| gpio_pinset_t *pinlist = NULL; |
| switch (priv->intf) |
| { |
| #ifdef CONFIG_SAMV7_AFEC0 |
| case 0: |
| pinlist = g_adcpinlist0; |
| break; |
| #endif |
| #ifdef CONFIG_SAMV7_AFEC1 |
| case 1: |
| pinlist = g_adcpinlist1; |
| break; |
| #endif |
| default: |
| /* We have already checked the intf number earlier in this function, |
| * so we should never get here. |
| */ |
| |
| return; |
| } |
| |
| /* Desible write protection (should already be disabled by default) */ |
| |
| afec_putreg(priv, SAM_AFEC_WPMR_OFFSET, AFEC_WPMR_WPKEY); |
| |
| /* Disable all channels */ |
| |
| afec_putreg(priv, SAM_AFEC_CHDR_OFFSET, AFEC_CHALL); |
| |
| gpio_pinset_t pinset = 0; |
| uint32_t afec_cher = 0; |
| uint32_t afec_cgr = 0; |
| for (int i = 0; i < priv->nchannels; i++) |
| { |
| DEBUGASSERT(priv->chanlist[i] < priv->max_pins); |
| pinset = pinlist[priv->chanlist[i]]; |
| sam_configgpio(pinset); |
| |
| afec_putreg(priv, SAM_AFEC_CSELR_OFFSET, |
| AFEC_CSELR_CSEL(priv->chanlist[i])); |
| afec_putreg(priv, SAM_AFEC_COCR_OFFSET, 0x200); |
| |
| afec_cher |= AFEC_CH(priv->chanlist[i]); |
| afec_cgr |= AFEC_CGR_GAIN(priv->chanlist[i], 0); |
| } |
| |
| /* Enable channels */ |
| |
| afec_putreg(priv, SAM_AFEC_CHER_OFFSET, afec_cher); |
| afec_putreg(priv, SAM_AFEC_CGR_OFFSET, afec_cgr); |
| |
| return; |
| |
| exit_leave_critical: |
| leave_critical_section(flags); |
| } |
| |
| /**************************************************************************** |
| * Name: afec_setup |
| * |
| * Description: |
| * Configure the ADC. This method is called the first time that the ADC |
| * device is opened. This will occur when the port is first opened. |
| * This setup includes configuring and attaching ADC interrupts. |
| * Interrupts are all disabled upon return. |
| * |
| ****************************************************************************/ |
| |
| static int afec_setup(struct adc_dev_s *dev) |
| { |
| struct samv7_dev_s *priv = (struct samv7_dev_s *)dev->ad_priv; |
| |
| /* Do nothing when the ADC device is already set up */ |
| |
| if (priv->initialized > 0) |
| { |
| priv->initialized++; |
| return OK; |
| } |
| |
| priv->initialized++; |
| |
| int ret = irq_attach(priv->irq, afec_interrupt, dev); |
| if (ret < 0) |
| { |
| ainfo("irq_attach failed: %d\n", ret); |
| return ret; |
| } |
| |
| up_enable_irq(priv->irq); |
| |
| /* Start the first conversion */ |
| |
| priv->current = 0; |
| |
| uint32_t afec_cselr = AFEC_CSELR_CSEL(priv->chanlist[priv->current]); |
| afec_putreg(priv, SAM_AFEC_CSELR_OFFSET, afec_cselr); |
| |
| #ifdef CONFIG_SAMV7_AFEC_DMA |
| /* Initiate DMA transfers */ |
| |
| priv->ready = true; /* Worker is available */ |
| priv->enabled = true; /* Transfers are enabled */ |
| |
| sam_afec_dmastart(dev); |
| #endif |
| |
| /* Setup AFEC trigger */ |
| |
| ret = sam_afec_trigger(priv); |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: afec_rxint |
| * |
| * Description: |
| * Call to enable or disable RX interrupts |
| * |
| ****************************************************************************/ |
| |
| static void afec_rxint(struct adc_dev_s *dev, bool enable) |
| { |
| struct samv7_dev_s *priv = (struct samv7_dev_s *)dev->ad_priv; |
| |
| #ifdef CONFIG_SAMV7_AFEC_DMA |
| /* Ignore redundant requests */ |
| |
| if (priv->enabled != enable) |
| { |
| /* Set a flag. If disabling, the DMA sequence will terminate at the |
| * completion of the next DMA. |
| */ |
| |
| priv->enabled = enable; |
| |
| /* If enabling, then we need to restart the DMA transfer */ |
| |
| sam_afec_dmastart(dev); |
| } |
| |
| #else |
| |
| uint32_t afec_ixr = 0; |
| |
| for (int i = 0; i < priv->nchannels; i++) |
| { |
| afec_ixr |= AFEC_INT_EOC(priv->chanlist[i]); |
| } |
| |
| /* Enable interrupts */ |
| |
| if (enable) |
| { |
| afec_putreg(priv, SAM_AFEC_IER_OFFSET, afec_ixr); |
| } |
| else |
| { |
| afec_putreg(priv, SAM_AFEC_IDR_OFFSET, afec_ixr); |
| } |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: afec_shutdown |
| * |
| * Description: |
| * Disable the ADC. This method is called when the ADC device is closed. |
| * This method reverses the operation the setup method. |
| * |
| ****************************************************************************/ |
| |
| static void afec_shutdown(struct adc_dev_s *dev) |
| { |
| struct samv7_dev_s *priv = (struct samv7_dev_s *)dev->ad_priv; |
| |
| /* Shutdown the ADC device only when not in use */ |
| |
| priv->initialized--; |
| |
| if (priv->initialized > 0) |
| { |
| return; |
| } |
| |
| /* Reset ADC driver */ |
| |
| afec_reset(dev); |
| |
| /* Disable ADC interrupts */ |
| |
| up_disable_irq(priv->irq); |
| |
| /* Then detach the ADC interrupt handler. */ |
| |
| irq_detach(priv->irq); |
| } |
| |
| /**************************************************************************** |
| * Name: afec_ioctl |
| * |
| * Description: |
| * All ioctl calls will be routed through this method. |
| * |
| * Input Parameters: |
| * dev - pointer to device structure used by the driver |
| * cmd - command |
| * arg - arguments passed with command |
| * |
| * Returned Value: |
| * OK in success or error value |
| * |
| ****************************************************************************/ |
| |
| static int afec_ioctl(struct adc_dev_s *dev, int cmd, unsigned long arg) |
| { |
| struct samv7_dev_s *priv = (struct samv7_dev_s *)dev->ad_priv; |
| int ret = OK; |
| |
| switch (cmd) |
| { |
| #ifdef CONFIG_SAMV7_AFEC_SWTRIG |
| case ANIOC_TRIGGER: |
| { |
| if (priv->trigger == AFEC_TRIGGER_SW) |
| { |
| afec_putreg(priv, SAM_AFEC_CR_OFFSET, AFEC_CR_START); |
| } |
| else |
| { |
| ret = -ENOTTY; |
| } |
| } |
| break; |
| #endif |
| case ANIOC_GET_NCHANNELS: |
| { |
| /* Return the number of configured channels */ |
| |
| ret = priv->nchannels; |
| } |
| break; |
| case ANIOC_SAMV7_AFEC_IOCTRL_GAIN: |
| { |
| /* Set the requested gain of the associated channel */ |
| |
| sam_afec_gain_param_tds *chgain = (sam_afec_gain_param_tds *) arg; |
| |
| uint32_t afec_cgr = afec_getreg(priv, SAM_AFEC_CGR_OFFSET); |
| afec_cgr &= ~AFEC_CGR_GAIN_MASK(chgain->channel); |
| afec_cgr |= AFEC_CGR_GAIN(chgain->channel, chgain->gain); |
| afec_putreg(priv, SAM_AFEC_CGR_OFFSET, afec_cgr); |
| |
| /* new gain is set, now adjust the offset register, use gain |
| * 1 as default and fallback |
| */ |
| |
| uint16_t offset; |
| if (chgain->gain == 1) |
| { |
| offset = 0x100; |
| } |
| else if (chgain->gain == 2) |
| { |
| offset = 0x40; |
| } |
| else |
| { |
| offset = 0x200; |
| } |
| |
| afec_putreg(priv, SAM_AFEC_CSELR_OFFSET, |
| AFEC_CSELR_CSEL(chgain->channel)); |
| |
| afec_putreg(priv, SAM_AFEC_COCR_OFFSET, offset); |
| } |
| break; |
| |
| default: |
| { |
| aerr("ERROR: Unknown cmd: %d\n", cmd); |
| ret = -ENOTTY; |
| } |
| break; |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: afec_interrupt |
| * |
| * Description: |
| * ADC interrupt handler |
| * |
| ****************************************************************************/ |
| |
| static int afec_interrupt(int irq, void *context, void *arg) |
| { |
| struct adc_dev_s *dev = (struct adc_dev_s *)arg; |
| struct samv7_dev_s *priv = (struct samv7_dev_s *)dev->ad_priv; |
| int32_t data; |
| |
| if ((afec_getreg(priv, SAM_AFEC_ISR_OFFSET) & |
| AFEC_CH(priv->chanlist[priv->current])) != 0) |
| { |
| /* Read data */ |
| |
| data = (int32_t)afec_getreg(priv, SAM_AFEC_CDR_OFFSET); |
| |
| if (priv->cb != NULL) |
| { |
| DEBUGASSERT(priv->cb->au_receive != NULL); |
| priv->cb->au_receive(dev, priv->chanlist[priv->current], data); |
| } |
| |
| /* Set the channel number of the next channel that will complete |
| * conversion. |
| */ |
| |
| priv->current++; |
| |
| if (priv->current >= priv->nchannels) |
| { |
| /* Restart the conversion sequence from the beginning */ |
| |
| priv->current = 0; |
| } |
| |
| /* Setup the next conversion */ |
| |
| uint32_t afec_cselr = AFEC_CSELR_CSEL(priv->chanlist[priv->current]); |
| afec_putreg(priv, SAM_AFEC_CSELR_OFFSET, afec_cselr); |
| } |
| |
| /* There are no interrupt flags left to clear */ |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: sam_afec_initialize |
| * |
| * Description: |
| * Initialize the adc |
| * |
| * Input Parameters: |
| * intf - ADC number (0 or 1) |
| * chanlist - The list of channels |
| * nchannels - Number of channels |
| * |
| * Returned Value: |
| * Valid can device structure reference on success; a NULL on failure |
| * |
| ****************************************************************************/ |
| |
| struct adc_dev_s *sam_afec_initialize(int intf, |
| const uint8_t *chanlist, |
| int nchannels) |
| { |
| struct adc_dev_s *dev; |
| struct samv7_dev_s *priv; |
| |
| DEBUGASSERT(nchannels > 0); |
| |
| switch (intf) |
| { |
| #ifdef CONFIG_SAMV7_AFEC0 |
| case 0: |
| { |
| dev = &g_adcdev0; |
| break; |
| } |
| #endif /* CONFIG_SAMV7_AFEC0 */ |
| |
| #ifdef CONFIG_SAMV7_AFEC1 |
| case 1: |
| { |
| dev = &g_adcdev1; |
| break; |
| } |
| #endif /* CONFIG_SAMV7_AFEC1 */ |
| |
| default: |
| { |
| aerr("ERROR: Tried to initialize invalid ADC: %d\n", intf); |
| return NULL; |
| } |
| } |
| |
| priv = (struct samv7_dev_s *)dev->ad_priv; |
| |
| priv->nchannels = nchannels; |
| memcpy(priv->chanlist, chanlist, nchannels); |
| |
| #ifdef CONFIG_SAMV7_AFEC_DMA |
| priv->nsamples = priv->nchannels * CONFIG_SAMV7_AFEC_DMASAMPLES; |
| priv->dma = sam_dmachannel(0, DMA_FLAGS | |
| DMACH_FLAG_PERIPHPID(priv->pid)); |
| #endif |
| |
| ainfo("intf: %d nchannels: %d\n", priv->intf, priv->nchannels); |
| |
| return dev; |
| } |
| |
| #endif /* CONFIG_SAMV7_AFEC0 || CONFIG_SAMV7_AFEC1 */ |
| |
| #endif /* CONFIG_ADC */ |