imxrt: add support for ADC triggering by an external signal
Config option IMXRT_ADCx_ETC can now be used to select an external HW trigger to be used instead of continous trigger. Continous trigger is used if IMXRT_ADCx_ETC = -1 (default option). Otherwise the source signal is routed through XBAR and used as a trigger. Hardware triggering is currently limited to maximum of 8 channels. HW trigger is automatically disabled if there are more than 8 channels. The external triggering was tested with PWM signal as a source. Signed-off-by: Michal Lenc <michallenc@seznam.cz>
This commit is contained in:
parent
5a085176fe
commit
9ebd7e525c
@ -87,6 +87,18 @@ ADC
|
||||
ADC driver with the successive approximation analog/digital converter. The lower-half of
|
||||
this driver is initialize by calling :c:func:`imxrt_adcinitialize`.
|
||||
|
||||
ADC module can use either continous trigger (next conversion is started as soon as the
|
||||
previous is finished) or hardware trigger. This option is selected by IMXRT_ADCx_ETC
|
||||
(x = 1, 2) config option. If IMXRT_ADCx_ETC = -1 then continous trigger is used. If
|
||||
corresponding XBAR number is put in IMXRT_ADCx_ETC then that signal is used to trigger
|
||||
the ADC conversion (for example PWM signal can be used as a source). For PWM XBAR options
|
||||
please refer to PWM chapter of this documentation.
|
||||
|
||||
Hardware triggering is currently limited to maximum of 8 channels. HW trigger is automatically
|
||||
disabled if there are more than 8 channels.
|
||||
|
||||
DMA is currently not supported for ADC modules.
|
||||
|
||||
CAN
|
||||
---
|
||||
|
||||
|
@ -1274,11 +1274,45 @@ menuconfig IMXRT_ADC1
|
||||
default n
|
||||
select IMXRT_ADC
|
||||
|
||||
if IMXRT_ADC1
|
||||
|
||||
config IMXRT_ADC1_ETC
|
||||
int "ADC1 External Trigger"
|
||||
range -1 130
|
||||
default -1
|
||||
---help---
|
||||
ADC module can be triggered by an external source (timer, PWM, etc).
|
||||
This config option selects the signal's source. The number in
|
||||
IMXRT_ADC1_ETC corresponds with the XBAR number of the source (please
|
||||
refer to the documentation for XBAR numbers).
|
||||
|
||||
-1 (default value) means the conversion is not to be triggered by an
|
||||
external source.
|
||||
|
||||
endif
|
||||
|
||||
menuconfig IMXRT_ADC2
|
||||
bool "ADC2"
|
||||
default n
|
||||
select IMXRT_ADC
|
||||
|
||||
if IMXRT_ADC2
|
||||
|
||||
config IMXRT_ADC2_ETC
|
||||
int "ADC2 External Trigger"
|
||||
range -1 130
|
||||
default -1
|
||||
---help---
|
||||
ADC module can be triggered by an external source (timer, PWM, etc).
|
||||
This config option selects the signal's source. The number in
|
||||
IMXRT_ADC2_ETC corresponds with the XBAR number of the source (please
|
||||
refer to the documentation for XBAR numbers).
|
||||
|
||||
-1 (default value) means the conversion is not to be triggered by an
|
||||
external source.
|
||||
|
||||
endif
|
||||
|
||||
endmenu
|
||||
|
||||
config IMXRT_SEMC
|
||||
|
@ -33,6 +33,7 @@
|
||||
****************************************************************************/
|
||||
|
||||
#define TRIG_OFFSET 0x28
|
||||
#define IRQ_OFFSET 0x10
|
||||
#define CHAIN_OFFSET 0x4
|
||||
#define RESULT_OFFSET 0x4
|
||||
|
||||
|
@ -42,9 +42,11 @@
|
||||
#include "arm_internal.h"
|
||||
#include "chip.h"
|
||||
#include "hardware/imxrt_adc.h"
|
||||
#include "hardware/imxrt_adc_etc.h"
|
||||
#include "hardware/imxrt_pinmux.h"
|
||||
#include "imxrt_gpio.h"
|
||||
#include "imxrt_periphclks.h"
|
||||
#include "imxrt_xbar.h"
|
||||
|
||||
#ifdef CONFIG_IMXRT_ADC
|
||||
|
||||
@ -69,9 +71,15 @@ struct imxrt_dev_s
|
||||
uint32_t base; /* ADC register base */
|
||||
uint8_t initialized; /* ADC initialization counter */
|
||||
int irq; /* ADC IRQ number */
|
||||
int irq_etc; /* ADC_ETC IRQ number */
|
||||
int nchannels; /* Number of configured ADC channels */
|
||||
uint8_t chanlist[ADC_MAX_CHANNELS]; /* ADC channel list */
|
||||
uint8_t current; /* Current channel being converted */
|
||||
uint8_t trig_num; /* First usable trigger number
|
||||
* (ADC1 = 0, ADC2 = 4)
|
||||
*/
|
||||
int16_t trig_src; /* Conversion trigger source */
|
||||
uint32_t trig_clear; /* ADC_ETC IRQ clear bit */
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
@ -83,6 +91,7 @@ static void adc_putreg(struct imxrt_dev_s *priv, uint32_t offset,
|
||||
static uint32_t adc_getreg(struct imxrt_dev_s *priv, uint32_t offset);
|
||||
static void adc_modifyreg(struct imxrt_dev_s *priv, uint32_t offset,
|
||||
uint32_t clearbits, uint32_t setbits);
|
||||
static int adc_reset_etc(struct imxrt_dev_s *priv);
|
||||
|
||||
/* ADC methods */
|
||||
|
||||
@ -113,9 +122,13 @@ static const struct adc_ops_s g_adcops =
|
||||
static struct imxrt_dev_s g_adcpriv1 =
|
||||
{
|
||||
.irq = IMXRT_IRQ_ADC1,
|
||||
.irq_etc = IMXRT_IRQ_ADCETC_0,
|
||||
.intf = 1,
|
||||
.initialized = 0,
|
||||
.base = IMXRT_ADC1_BASE,
|
||||
.trig_num = 0,
|
||||
.trig_src = CONFIG_IMXRT_ADC1_ETC,
|
||||
.trig_clear = ADC_ETC_DONE01_IRQ_TRIG0_DONE0,
|
||||
};
|
||||
|
||||
static struct adc_dev_s g_adcdev1 =
|
||||
@ -149,9 +162,13 @@ gpio_pinset_t g_adcpinlist1[ADC_MAX_CHANNELS] =
|
||||
static struct imxrt_dev_s g_adcpriv2 =
|
||||
{
|
||||
.irq = IMXRT_IRQ_ADC2,
|
||||
.irq_etc = IMXRT_IRQ_ADCETC_1,
|
||||
.intf = 2,
|
||||
.initialized = 0,
|
||||
.base = IMXRT_ADC2_BASE,
|
||||
.trig_num = 4,
|
||||
.trig_src = CONFIG_IMXRT_ADC2_ETC,
|
||||
.trig_clear = ADC_ETC_DONE01_IRQ_TRIG4_DONE1,
|
||||
};
|
||||
|
||||
static struct adc_dev_s g_adcdev2 =
|
||||
@ -202,6 +219,117 @@ static void adc_modifyreg(struct imxrt_dev_s *priv, uint32_t offset,
|
||||
modifyreg32(priv->base + offset, clearbits, setbits);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: adc_reset_etc
|
||||
*
|
||||
* Description:
|
||||
* Setup registers and channels/triggers for external trigering of ADC
|
||||
* conversion. This functions also takes care of connecting XBARs.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int adc_reset_etc(struct imxrt_dev_s *priv)
|
||||
{
|
||||
uint32_t adc_cfg;
|
||||
uint32_t regval;
|
||||
uint8_t chain_num;
|
||||
uint8_t chain_last;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
ret = OK;
|
||||
|
||||
/* Enable HW trigger */
|
||||
|
||||
adc_cfg = adc_getreg(priv, IMXRT_ADC_CFG_OFFSET);
|
||||
adc_cfg |= ADC_CFG_ADTRG_HW;
|
||||
adc_putreg(priv, IMXRT_ADC_CFG_OFFSET, adc_cfg);
|
||||
|
||||
/* Disable soft reset first */
|
||||
|
||||
putreg32(0, IMXRT_ADCETC_BASE + IMXRT_ADC_ETC_CTRL_OFFSET);
|
||||
|
||||
/* Set trigger: Trigger 0 for ADC1 and trigger 4 for ADC2 */
|
||||
|
||||
regval = ADC_ETC_CTRL_TRIG_EN(1 << priv->trig_num);
|
||||
putreg32(regval, IMXRT_ADCETC_BASE + IMXRT_ADC_ETC_CTRL_OFFSET);
|
||||
|
||||
/* 0 means trigger length is 1 so we need to set priv->nchannels -1
|
||||
* length. Priority is set to 7 (highest).
|
||||
*/
|
||||
|
||||
regval = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(priv->nchannels - 1) |
|
||||
ADC_ETC_TRIG_CTRL_TRIG_PR(7);
|
||||
putreg32(regval, IMXRT_ADCETC_BASE + IMXRT_ADC_ETC_TRIG_CTRL_OFFSET
|
||||
+ TRIG_OFFSET * priv->trig_num);
|
||||
|
||||
chain_num = 0;
|
||||
chain_last = 0;
|
||||
for (i = 0; i < priv->nchannels; i++)
|
||||
{
|
||||
/* Check whether this is the last chain to be added */
|
||||
|
||||
if (i == priv->nchannels - 1)
|
||||
{
|
||||
/* If this is last chain we need to enable interrupt for it.
|
||||
* 1 for ADC1, 2 for ADC0.
|
||||
*/
|
||||
|
||||
chain_last = priv->intf;
|
||||
}
|
||||
|
||||
regval = getreg32(IMXRT_ADCETC_BASE +
|
||||
IMXRT_ADC_ETC_TRIG_CHAIN_OFFSET +
|
||||
TRIG_OFFSET * priv->trig_num +
|
||||
CHAIN_OFFSET * chain_num);
|
||||
|
||||
/* Set up chain registers */
|
||||
|
||||
if (i % 2 == 0)
|
||||
{
|
||||
regval &= ~(ADC_ETC_TRIG_CHAIN_CSEL0_MASK |
|
||||
ADC_ETC_TRIG_CHAIN_HTWS0_MASK |
|
||||
ADC_ETC_TRIG_CHAIN_IE0_MASK |
|
||||
ADC_ETC_TRIG_CHAIN_B2B0);
|
||||
regval |= ADC_ETC_TRIG_CHAIN_CSEL0(priv->chanlist[i]) |
|
||||
ADC_ETC_TRIG_CHAIN_HWTS0(1 << i) |
|
||||
ADC_ETC_TRIG_CHAIN_IE0(chain_last) |
|
||||
ADC_ETC_TRIG_CHAIN_B2B0;
|
||||
putreg32(regval, IMXRT_ADCETC_BASE +
|
||||
IMXRT_ADC_ETC_TRIG_CHAIN_OFFSET +
|
||||
TRIG_OFFSET * priv->trig_num +
|
||||
CHAIN_OFFSET * chain_num);
|
||||
}
|
||||
else
|
||||
{
|
||||
regval &= ~(ADC_ETC_TRIG_CHAIN_CSEL1_MASK |
|
||||
ADC_ETC_TRIG_CHAIN_HTWS1_MASK |
|
||||
ADC_ETC_TRIG_CHAIN_IE1_MASK |
|
||||
ADC_ETC_TRIG_CHAIN_B2B1);
|
||||
regval |= ADC_ETC_TRIG_CHAIN_CSEL1(priv->chanlist[i]) |
|
||||
ADC_ETC_TRIG_CHAIN_HWTS1(1 << i) |
|
||||
ADC_ETC_TRIG_CHAIN_IE1(chain_last) |
|
||||
ADC_ETC_TRIG_CHAIN_B2B1;
|
||||
putreg32(regval, IMXRT_ADCETC_BASE +
|
||||
IMXRT_ADC_ETC_TRIG_CHAIN_OFFSET +
|
||||
TRIG_OFFSET * priv->trig_num +
|
||||
CHAIN_OFFSET * chain_num);
|
||||
chain_num += 1;
|
||||
}
|
||||
}
|
||||
|
||||
ret = imxrt_xbar_connect(IMXRT_XBARA1_OUT_ADC_ETC_XBAR0_TRIG0_SEL_OFFSET
|
||||
+ priv->trig_num,
|
||||
IMXRT_XBARA1(XBAR_OUTPUT, priv->trig_src));
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
aerr("ERROR: imxrt_xbar_connect failed: %d\n", ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: adc_bind
|
||||
*
|
||||
@ -234,6 +362,7 @@ static void adc_reset(struct adc_dev_s *dev)
|
||||
{
|
||||
struct imxrt_dev_s *priv = (struct imxrt_dev_s *)dev->ad_priv;
|
||||
irqstate_t flags;
|
||||
int ret;
|
||||
|
||||
flags = enter_critical_section();
|
||||
|
||||
@ -333,6 +462,31 @@ static void adc_reset(struct adc_dev_s *dev)
|
||||
imxrt_config_gpio(pinset);
|
||||
}
|
||||
|
||||
/* priv->nchannels > 8 check is used because there are only 8 control
|
||||
* registers for HW triggers. HW triggering is not yet implemented if
|
||||
* more than 8 ADC channels are used.
|
||||
*/
|
||||
|
||||
if ((priv->trig_src != -1) && (priv->nchannels > 8))
|
||||
{
|
||||
/* Disable external triggering */
|
||||
|
||||
aerr("Cannot use HW trigger as %d channels are used. "
|
||||
"HW trigger can be used for 8 or less channels. "
|
||||
"Switched to software trigger.\n",
|
||||
priv->nchannels);
|
||||
priv->trig_src = -1;
|
||||
}
|
||||
|
||||
if (priv->trig_src != -1)
|
||||
{
|
||||
ret = adc_reset_etc(priv);
|
||||
if (ret < 0)
|
||||
{
|
||||
aerr("ERROR: adc_reset_etc() failed: %d.\n", ret);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
exit_leave_critical:
|
||||
@ -353,6 +507,7 @@ exit_leave_critical:
|
||||
static int adc_setup(struct adc_dev_s *dev)
|
||||
{
|
||||
struct imxrt_dev_s *priv = (struct imxrt_dev_s *)dev->ad_priv;
|
||||
int ret;
|
||||
|
||||
/* Do nothing when the ADC device is already set up */
|
||||
|
||||
@ -363,20 +518,45 @@ static int adc_setup(struct adc_dev_s *dev)
|
||||
|
||||
priv->initialized++;
|
||||
|
||||
int ret = irq_attach(priv->irq, adc_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;
|
||||
adc_putreg(priv, IMXRT_ADC_HC0_OFFSET,
|
||||
ADC_HC_ADCH(priv->chanlist[priv->current]));
|
||||
|
||||
if (priv->trig_src != -1)
|
||||
{
|
||||
/* Atach and enable ADCETC interrupt */
|
||||
|
||||
ret = irq_attach(priv->irq_etc, adc_interrupt, dev);
|
||||
if (ret < 0)
|
||||
{
|
||||
aerr("irq_attach for %d failed: %d\n", IMXRT_IRQ_ADCETC_0, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
up_enable_irq(priv->irq_etc);
|
||||
|
||||
/* For every used channel set ADC_ETC trigger */
|
||||
|
||||
for (int i = 0; i < priv->nchannels; i++)
|
||||
{
|
||||
adc_putreg(priv, IMXRT_ADC_HC0_OFFSET + 4 * i,
|
||||
ADC_HC_ADCH_EXT_ADC_ETC);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = irq_attach(priv->irq, adc_interrupt, dev);
|
||||
if (ret < 0)
|
||||
{
|
||||
ainfo("irq_attach for %d failed: %d\n", priv->irq, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
up_enable_irq(priv->irq);
|
||||
|
||||
/* Start the first conversion */
|
||||
|
||||
adc_putreg(priv, IMXRT_ADC_HC0_OFFSET,
|
||||
ADC_HC_ADCH(priv->chanlist[priv->current]));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -407,6 +587,14 @@ static void adc_shutdown(struct adc_dev_s *dev)
|
||||
* level of the NVIC.
|
||||
*/
|
||||
|
||||
if (priv->trig_src != -1)
|
||||
{
|
||||
/* Disable and detach an interrupt for ADC_ETC module */
|
||||
|
||||
up_disable_irq(priv->irq_etc);
|
||||
irq_detach(priv->irq_etc);
|
||||
}
|
||||
|
||||
/* Disable interrupt and stop any on-going conversion */
|
||||
|
||||
adc_putreg(priv, IMXRT_ADC_HC0_OFFSET, ~ADC_HC_AIEN | ADC_HC_ADCH_DIS);
|
||||
@ -506,7 +694,7 @@ static int adc_interrupt(int irq, void *context, void *arg)
|
||||
if (priv->cb != NULL)
|
||||
{
|
||||
DEBUGASSERT(priv->cb->au_receive != NULL);
|
||||
priv->cb->au_receive(dev, priv->chanlist[priv->current], data);
|
||||
priv->cb->au_receive(dev, priv->chanlist[priv->current], data);
|
||||
}
|
||||
|
||||
/* Set the channel number of the next channel that will complete
|
||||
@ -527,6 +715,45 @@ static int adc_interrupt(int irq, void *context, void *arg)
|
||||
adc_modifyreg(priv, IMXRT_ADC_HC0_OFFSET, ADC_HC_ADCH_MASK,
|
||||
ADC_HC_ADCH(priv->chanlist[priv->current]));
|
||||
}
|
||||
else if ((getreg32(IMXRT_ADCETC_BASE + IMXRT_ADC_ETC_DONE01_IRQ_OFFSET)
|
||||
& priv->trig_clear) != 0)
|
||||
{
|
||||
/* Read data */
|
||||
|
||||
for (int i = 0; i < priv->nchannels; i++)
|
||||
{
|
||||
data = (int32_t)adc_getreg(priv, IMXRT_ADC_R0_OFFSET + 4 * i);
|
||||
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;
|
||||
}
|
||||
|
||||
adc_modifyreg(priv, IMXRT_ADC_HC0_OFFSET + 4 * i,
|
||||
ADC_HC_ADCH_MASK, ADC_HC_ADCH_EXT_ADC_ETC);
|
||||
}
|
||||
|
||||
/* Clear the interrpt bit. The interrupt access is a little bit
|
||||
* unfortunate here. We need to access ADC_ETC_DONE01_IRQ_TRIG0_DONE0
|
||||
* for ADC1 and ADC_ETC_DONE01_IRQ_TRIG4_DONE1 for ADC2 and since we
|
||||
* want to avoid unnecessary if statemants we need to .
|
||||
*/
|
||||
|
||||
putreg32(priv->trig_clear, IMXRT_ADCETC_BASE
|
||||
+ IMXRT_ADC_ETC_DONE01_IRQ_OFFSET);
|
||||
}
|
||||
|
||||
/* There are no interrupt flags left to clear */
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user