Ok I finally figured this all out. For anybody who is curious, here is the solution.
The project is fairly simple:
Turns on the ADC for continuous (as fast as possible) conversion. The ADC conversions trigger the DMA to transfer data to the DAC. At the maximum clock frequency I can get ~600kHz sampling rate out. This is less than the 825kHz specified for the ADC but I'm sure I could tweak something to go faster if needed.
Configuring the DAC is simple:
Turn on the DAC clock and enable the DAC.
Configuring the ADC is also fairly simple.
Turn on the ADC clock and then set the following:
CFG1 register
MODE = 1 (12 bit mode - the DAC is only 12 bits so be sure to set this or you'll get wrap-around),
ADICLK = 0 (input bus clock = 24MHz),
ADIV = 1 (divide by 2)
SC2 register
DMAEN = 1 (enable DMA)
SC3 register
ADCO = 1 (enable continuous conversions)
SC1A register
Set input channel you are using, I set ADCH = 8
Configuring the DMA is the only tricky part. I think there are some bugs that Kinetis needs to address.
1. Enable the DMAMUX and DMA clocks
2. Turn off the DMAMUX (so you can set up the DMA and DMAMUX)
3. Clear any pending errors and the done mask (DMA_DSR_BCR0 |= DMA_DSR_BCR_DONE_MASK;)
4. Set the source and destination for the DMA:
Source (SAR0) = ADC0_RA Destination (DAR0) = DAC0_DATA0L
5. Set the # bytes you want to transfer before interrupting (yes, you HAVE to interrupt, no way around it. I think this is a bug... explained below)
DMA_DSR_BCR0 = 1048574 (My dma transfers are 2 bytes, so this # must be a multiple of 2)
6. Set up the DCR with the following:
Enable External interrupts (EINT) (Need to reset the BCR and DONE bit to allow continuous operation (bug explained below))
Enable Peripheral Request (ERQ) (Allow the ADC to trigger the DMA)
Force a single read/write transfer per request (CS) (this makes sure the DMA only transfers on ADC trigger)
Reset the ERQ bit when BCR is exhausted (D_REQ) (this makes sure the ADC does not try to trigger an interrupt if BCR is empty, it should not be needed but it is)
Set source and destination size to 2 bytes (SSIZE, DSIZE = 2)
7. Configure the DMAMUX to use the ADC0 channel as the trigger and enable the DMA (ENBL = 1, SOURCE = 40)
There is only one interrupt routine required, the DMA interrupt. It is configured as follows:
1. Clear the done bit (DMA_DSR_BCR0 |= DMA_DSR_BCR_DONE_MASK)
2. reset the BCR count register (I set it back up to 1048574 to have minimal interrupt time) (DMA_DSR_BCR0 |= DMA_DSR_BCR_BCR(1048574))
3. Re-enable peripheral requests to allow ADC to trigger DMA again (DMA_DCR0 |= DMA_DCR_ERQ_MASK)
That's it. It will work and automatically send ADC data to the DAC. The speed at which it does this is dependent on how you set up your clocks in the CPU, but I was able to transfer at a rate of ~600kHz (ADC-DAC).
Bug in the DMA?
To my mind, the CS bit (cycle steal) should allow the DMA to run whether the BCR register is 0 or not. It SHOULD force a transfer of data from the source to the destination (ADC-DAC in my case). But if the BCR register is 0, it does not. This is the only reason why we need an interrupt. To reset the BCR register and clear the DONE bit.
Also, if the D_REQ is not set (i.e. if peripheral requests are not disabled when the BCR is exhausted), then when the ADC tries to trigger the DMA (on an empty BCR register) it causes an error (the CE bit goes high) on the BCR. I don't really understand why, but I've accepted it, and as long as you re-enable peripheral requests (ERQ) it will work continously.
Summing up
I believe this is the most efficient way to grab data from the ADC and pipe it to some peripheral or location in memory. It requires very little processor time (the interrupt occurs very infrequently, less than 1 interrupt per second) and the interrupt itself has minimal instructions (although you can add a few if you want to toggle a peripheral GPIO or transfer some data elsewhere).
I hope this helps other people. Code is attached.