Coldfire targets: Shuffle IRQ levels around to have all interaction between low level audio function calls and DMA be atomic. Make recording and playback independently startable and stoppable so one can be running and not interfere with the other. All tests I can do at the moment check out ok (play, record, play+record, FM radio on iRivers, S/PDIF on H120 (w/running optical on/off), and on-the-fly samplerate changes). Recording tested for well over an hour run and no problems.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@12658 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index 56f845a..49510f5 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -19,6 +19,8 @@
 vbrfix.c
 viewer.c
 
+ringmod.c
+
 /* plugins built for all targets, but not for the simulator */
 #if !defined(SIMULATOR)
 metronome.c
diff --git a/firmware/target/coldfire/iaudio/m5/audio-m5.c b/firmware/target/coldfire/iaudio/m5/audio-m5.c
index 9531f93..2442351 100644
--- a/firmware/target/coldfire/iaudio/m5/audio-m5.c
+++ b/firmware/target/coldfire/iaudio/m5/audio-m5.c
@@ -23,6 +23,7 @@
 
 void audio_set_output_source(int source)
 {
+    int level = set_irq_level(DMA_IRQ_LEVEL);
     unsigned long txsrc;
 
     if ((unsigned)source >= AUDIO_NUM_SOURCES)
@@ -31,6 +32,7 @@
         txsrc = (4 << 8); /* recording, iis1RcvData */
 
     IIS1CONFIG = (IIS1CONFIG & ~(7 << 8)) | txsrc;
+    set_irq_level(level);
 } /* audio_set_output_source */
 
 void audio_set_source(int source, unsigned flags)
diff --git a/firmware/target/coldfire/iaudio/m5/power-m5.c b/firmware/target/coldfire/iaudio/m5/power-m5.c
index c689488..f3e7cdd 100644
--- a/firmware/target/coldfire/iaudio/m5/power-m5.c
+++ b/firmware/target/coldfire/iaudio/m5/power-m5.c
@@ -60,7 +60,7 @@
 void power_off(void)
 {
     lcd_remote_poweroff();
-    set_irq_level(HIGHEST_IRQ_LEVEL);
+    set_irq_level(DISABLE_INTERRUPTS);
     and_l(~0x00000008, &GPIO_OUT); /* Set KEEPACT low */
     asm("halt");
 }
diff --git a/firmware/target/coldfire/iaudio/x5/audio-x5.c b/firmware/target/coldfire/iaudio/x5/audio-x5.c
index e673d4a..91100ed 100644
--- a/firmware/target/coldfire/iaudio/x5/audio-x5.c
+++ b/firmware/target/coldfire/iaudio/x5/audio-x5.c
@@ -23,6 +23,7 @@
 
 void audio_set_output_source(int source)
 {
+    int level = set_irq_level(DMA_IRQ_LEVEL);
     unsigned long txsrc;
 
     if ((unsigned)source >= AUDIO_NUM_SOURCES)
@@ -31,6 +32,7 @@
         txsrc = (4 << 8); /* recording, iis1RcvData */
 
     IIS1CONFIG = (IIS1CONFIG & ~(7 << 8)) | txsrc;
+    set_irq_level(level);
 } /* audio_set_output_source */
 
 void audio_set_source(int source, unsigned flags)
diff --git a/firmware/target/coldfire/iaudio/x5/power-x5.c b/firmware/target/coldfire/iaudio/x5/power-x5.c
index 4d44500..c646570 100644
--- a/firmware/target/coldfire/iaudio/x5/power-x5.c
+++ b/firmware/target/coldfire/iaudio/x5/power-x5.c
@@ -60,7 +60,7 @@
 void power_off(void)
 {
     lcd_remote_poweroff();
-    set_irq_level(HIGHEST_IRQ_LEVEL);
+    set_irq_level(DISABLE_INTERRUPTS);
     and_l(~0x00000008, &GPIO_OUT); /* Set KEEPACT low */
     asm("halt");
 }
diff --git a/firmware/target/coldfire/iriver/audio-iriver.c b/firmware/target/coldfire/iriver/audio-iriver.c
index 2df7a21..bd07143 100644
--- a/firmware/target/coldfire/iriver/audio-iriver.c
+++ b/firmware/target/coldfire/iriver/audio-iriver.c
@@ -34,10 +34,14 @@
     #endif
     };
 
+    int level = set_irq_level(DMA_IRQ_LEVEL);
+
     if ((unsigned)source >= AUDIO_NUM_SOURCES)
         source = AUDIO_SRC_PLAYBACK;
 
     IIS2CONFIG = (IIS2CONFIG & ~(7 << 8)) | (txsrc_select[source+1] << 8);
+
+    set_irq_level(level);
 } /* audio_set_output_source */
 
 void audio_set_source(int source, unsigned flags)
diff --git a/firmware/target/coldfire/iriver/h100/power-h100.c b/firmware/target/coldfire/iriver/h100/power-h100.c
index 75c8bea..ce1c350 100644
--- a/firmware/target/coldfire/iriver/h100/power-h100.c
+++ b/firmware/target/coldfire/iriver/h100/power-h100.c
@@ -119,7 +119,7 @@
 
 void power_off(void)
 {
-    set_irq_level(HIGHEST_IRQ_LEVEL);
+    set_irq_level(DISABLE_INTERRUPTS);
     and_l(~0x00080000, &GPIO1_OUT);
     asm("halt");
     while(1)
diff --git a/firmware/target/coldfire/iriver/h100/spdif-h100.c b/firmware/target/coldfire/iriver/h100/spdif-h100.c
index 35508d1..ee4a940 100644
--- a/firmware/target/coldfire/iriver/h100/spdif-h100.c
+++ b/firmware/target/coldfire/iriver/h100/spdif-h100.c
@@ -63,30 +63,42 @@
     };
 
     bool kick;
+    int level;
+    int iis2config = 0;
 
     if ((unsigned)source >= ARRAYLEN(ebu1_config))
         source = AUDIO_SRC_PLAYBACK;
 
     spdif_source = source;
     spdif_on     = spdif_powered() && src_on;
-    kick         = spdif_on && source == AUDIO_SRC_PLAYBACK;
+
+    /* Keep a DMA interrupt initiated stop from changing play state */
+    level = set_irq_level(DMA_IRQ_LEVEL);
+
+    kick         = spdif_on && source == AUDIO_SRC_PLAYBACK
+                   && (DCR0 & DMA_EEXT);
 
     /* FIFO must be in reset condition to reprogram bits 15-12 */
     or_l(0x800, &EBU1CONFIG);
 
     if (kick)
+    {
+        iis2config = IIS2CONFIG;
         or_l(0x800, &IIS2CONFIG); /* Have to resync IIS2 TXSRC */
+    }
 
     /* Tranceiver must be powered or else monitoring will be disabled.
        CLOCKSEL bits only have relevance to normal operation so just
        set them always. */
     EBU1CONFIG = (spdif_on ? ebu1_config[source + 1] : 0) | (7 << 12);
 
-    if (kick && (DCR0 & DMA_EEXT)) /* only if still playing */
+    if (kick)
     {
-        and_l(~0x800, &IIS2CONFIG);
+        IIS2CONFIG = iis2config;
         PDOR3 = 0; /* A write to the FIFO kick-starts playback */
     }
+
+    set_irq_level(level);
 } /* spdif_set_output_source */
 
 /* Return the last set S/PDIF audio source */
diff --git a/firmware/target/coldfire/iriver/h300/power-h300.c b/firmware/target/coldfire/iriver/h300/power-h300.c
index 2875fa2..5e57326 100644
--- a/firmware/target/coldfire/iriver/h300/power-h300.c
+++ b/firmware/target/coldfire/iriver/h300/power-h300.c
@@ -91,7 +91,7 @@
 
 void power_off(void)
 {
-    set_irq_level(HIGHEST_IRQ_LEVEL);
+    set_irq_level(DISABLE_INTERRUPTS);
     and_l(~0x00080000, &GPIO1_OUT);
     asm("halt");
     while(1)
diff --git a/firmware/target/coldfire/pcm-coldfire.c b/firmware/target/coldfire/pcm-coldfire.c
index 2addcb9..ef84700 100644
--- a/firmware/target/coldfire/pcm-coldfire.c
+++ b/firmware/target/coldfire/pcm-coldfire.c
@@ -55,6 +55,21 @@
 #define IIS_PLAY        IIS2CONFIG
 #endif
 
+static bool is_playback_monitoring(void)
+{
+    return (IIS_PLAY & (7 << 8)) == (3 << 8);
+}
+
+static void iis_play_reset_if_playback(bool if_playback)
+{
+    bool is_playback = is_playback_monitoring();
+
+    if (is_playback != if_playback)
+        return;
+
+    or_l(IIS_FIFO_RESET, &IIS_PLAY);
+}
+
 #define PLLCR_SET_AUDIO_BITS_DEFPARM \
             ((freq_ent[FPARM_CLSEL] << 28) | (1 << 22))
 
@@ -113,9 +128,10 @@
 } /* pcm_set_frequency */
 
 /* apply audio settings */
-void _pcm_apply_settings(bool clear_reset)
+bool _pcm_apply_settings(bool clear_reset)
 {
     static int last_pcm_freq = 0;
+    bool did_reset = false;
  
     if (pcm_freq != last_pcm_freq)
     {
@@ -125,6 +141,7 @@
         or_l(IIS_FIFO_RESET, &IIS_PLAY);
         audiohw_set_frequency(freq_ent[FPARM_FSEL]);
         coldfire_set_pllcr_audio_bits(PLLCR_SET_AUDIO_BITS_DEFPARM);
+        did_reset = true;
     }
 
     SET_IIS_PLAY(IIS_PLAY_DEFPARM |
@@ -132,12 +149,25 @@
 #if 0
     logf("IISPLAY: %08X", IIS_PLAY);
 #endif
+
+    return did_reset;
 } /* _pcm_apply_settings */
 
-/* This clears the reset bit to enable monitoring immediately */
+/* This clears the reset bit to enable monitoring immediately if monitoring
+   recording sources or always if playback is in progress - we might be 
+   switching samplerates on the fly */
 void pcm_apply_settings(void)
 {
-    _pcm_apply_settings(true);
+    int level = set_irq_level(DMA_IRQ_LEVEL);
+    bool pbm = is_playback_monitoring();
+    bool kick = (DCR0 & DMA_EEXT) != 0 && pbm;
+
+    /* Clear reset if not playback monitoring or peripheral request is
+       active and playback monitoring */
+    if (_pcm_apply_settings(!pbm || kick) && kick)
+        PDOR3 = 0; /* Kick FIFO out of reset by writing to it */
+
+    set_irq_level(level);
 } /* pcm_apply_settings */
 
 /** DMA **/
@@ -149,11 +179,17 @@
 /* Set up the DMA transfer that kicks in when the audio FIFO gets empty */
 void pcm_play_dma_start(const void *addr, size_t size)
 {
+    int level;
+
     logf("pcm_play_dma_start");
 
     addr = (void *)((unsigned long)addr & ~3); /* Align data */
     size &= ~3; /* Size must be multiple of 4 */
 
+    /* If a tranfer is going, prevent an interrupt while setting up
+       a new one */
+    level = set_irq_level(DMA_IRQ_LEVEL);
+
     pcm_playing = true;
 
     /* Set up DMA transfer  */
@@ -162,28 +198,38 @@
     BCR0 = (unsigned long)size;   /* Bytes to transfer   */
 
     /* Enable the FIFO and force one write to it */
-    pcm_apply_settings();
+    _pcm_apply_settings(is_playback_monitoring());
 
     DCR0 = DMA_INT | DMA_EEXT | DMA_CS | DMA_AA |
            DMA_SINC | DMA_SSIZE(DMA_SIZE_LINE) | DMA_START;
+
+    set_irq_level(level);
 } /* pcm_play_dma_start */
 
 /* Stops the DMA transfer and interrupt */
-void pcm_play_dma_stop(void)
+static void pcm_play_dma_stop_irq(void)
 {
-#if 0
-    logf("pcm_play_dma_stop");
-#endif
-
     pcm_playing = false;
 
     DSR0 = 1;
     DCR0 = 0;
 
-    /* Place FIFO in reset condition */
-    or_l(IIS_FIFO_RESET, &IIS_PLAY);
+    /* Place TX FIFO in reset condition if playback monitoring is on.
+       Recording monitoring something else should not be stopped. */
+    iis_play_reset_if_playback(true);
 
     pcm_playing = false;    
+} /* pcm_play_dma_stop_irq */
+
+void pcm_play_dma_stop(void)
+{
+    int level = set_irq_level(DMA_IRQ_LEVEL);
+
+    logf("pcm_play_dma_stop");
+
+    pcm_play_dma_stop_irq();
+
+    set_irq_level(level);
 } /* pcm_play_dma_stop */
 
 void pcm_init(void)
@@ -236,8 +282,8 @@
        (DAC should be at zero point now). */
     audiohw_mute(false);
 
-    /* Enable interrupt at level 7, priority 0 */
-    ICR6 = (7 << 2);
+    /* Enable interrupt at level 6, priority 0 */
+    ICR6 = (6 << 2);
     and_l(~(1 << 14), &IMR); /* bit 14 is DMA0 */
 } /* pcm_init */
 
@@ -292,7 +338,7 @@
 #endif
     }
 
-    pcm_play_dma_stop();
+    pcm_play_dma_stop_irq();
 } /* DMA0 */
 
 /****************************************************************************
@@ -300,17 +346,21 @@
  **/
 void pcm_rec_dma_start(void *addr, size_t size)
 {
+    int level;
     logf("pcm_rec_dma_start");
 
     addr = (void *)((unsigned long)addr & ~3); /* Align data */
     size &= ~3; /* Size must be multiple of 4 */
 
+    /* No DMA1 interrupts while setting up a new transfer */
+    level = set_irq_level(DMA_IRQ_LEVEL);
+
     pcm_recording = true;
 
     and_l(~PDIR2_FIFO_RESET, &DATAINCONTROL);
-    /* Clear reset bit if the source is not set to monitor playback
+    /* Clear TX FIFO reset bit if the source is not set to monitor playback
        otherwise maintain independence between playback and recording. */
-    _pcm_apply_settings((IIS_PLAY & (7 << 8)) != (3 << 8));
+    _pcm_apply_settings(!is_playback_monitoring());
 
     /* Start the DMA transfer.. */
 #ifdef HAVE_SPDIF_IN
@@ -324,21 +374,35 @@
 
     DCR1 = DMA_INT | DMA_EEXT | DMA_CS | DMA_AA | DMA_DINC |
            DMA_DSIZE(DMA_SIZE_LINE) /* | DMA_START */;
+
+    set_irq_level(level);
 } /* pcm_dma_start */
 
+static void pcm_rec_dma_stop_irq(void)
+{
+    DSR1 = 1;         /* Clear interrupt */
+    DCR1 = 0;
+    pcm_recording = false;
+    or_l(PDIR2_FIFO_RESET, &DATAINCONTROL);
+
+    iis_play_reset_if_playback(false);
+} /* pcm_rec_dma_stop_irq */
+
 void pcm_rec_dma_stop(void)
 {
+    int level = set_irq_level(DMA_IRQ_LEVEL);
+
     logf("pcm_rec_dma_stop");
 
-    DSR1 = 1;         /* Clear interrupt */
-    DCR1 = 0;
+    pcm_rec_dma_stop_irq();
 
-    pcm_recording = false;
-    or_l(PDIR2_FIFO_RESET, &DATAINCONTROL);
-} /* pcm_dma_stop */
+    set_irq_level(level);
+} /* pcm_rec_dma_stop */
 
 void pcm_init_recording(void)
 {
+    int level = set_irq_level(DMA_IRQ_LEVEL);
+
     logf("pcm_init_recording");
 
     pcm_recording           = false;
@@ -349,21 +413,27 @@
     and_l(0xffff00ff, &DMAROUTE);
     or_l(DMA1_REQ_AUDIO_2, &DMAROUTE);
 
-    pcm_rec_dma_stop();
+    pcm_rec_dma_stop_irq();
 
-    ICR7 = (7 << 2);         /* Enable interrupt at level 7, priority 0    */
+    ICR7 = (6 << 2) | (1 << 0); /* Enable interrupt at level 6, priority 1 */
     and_l(~(1 << 15), &IMR); /* bit 15 is DMA1                             */
+
+    set_irq_level(level);
 } /* pcm_init_recording */
 
 void pcm_close_recording(void)
 {
+    int level = set_irq_level(DMA_IRQ_LEVEL);
+
     logf("pcm_close_recording");
 
-    pcm_rec_dma_stop();
+    pcm_rec_dma_stop_irq();
 
     and_l(0xffff00ff, &DMAROUTE);
     ICR7 = 0x00;            /* Disable interrupt */
     or_l((1 << 15), &IMR);  /* bit 15 is DMA1    */
+
+    set_irq_level(level);
 } /* pcm_close_recording */
 
 /* DMA1 Interrupt is called when the DMA has finished transfering a chunk
@@ -409,10 +479,10 @@
     logf("DMA1 done:%04x %d", res, status);
 #endif
     /* Finished recording */
-    pcm_rec_dma_stop();
+    pcm_rec_dma_stop_irq();
 } /* DMA1 */
 
-/* Continue transferring data in */
+/* Continue transferring data in - call from interrupt callback */
 void pcm_record_more(void *start, size_t size)
 {
     rec_peak_addr = (unsigned long *)start; /* Start peaking at dest */
@@ -431,15 +501,23 @@
 void pcm_play_pause_pause(void)
 {
     /* Disable DMA peripheral request. */
+    int level = set_irq_level(DMA_IRQ_LEVEL);
+
     and_l(~DMA_EEXT, &DCR0);
-    or_l(IIS_FIFO_RESET, &IIS_PLAY);
+    iis_play_reset_if_playback(true);
+
+    set_irq_level(level);
 } /* pcm_play_pause_pause */
 
 void pcm_play_pause_unpause(void)
 {
+    int level = set_irq_level(DMA_IRQ_LEVEL);
+
     /* Enable the FIFO and force one write to it */
-    pcm_apply_settings();
+    _pcm_apply_settings(is_playback_monitoring());
     or_l(DMA_EEXT | DMA_START, &DCR0);
+
+    set_irq_level(level);
 } /* pcm_play_pause_unpause */
 
 /**
diff --git a/firmware/target/coldfire/system-coldfire.c b/firmware/target/coldfire/system-coldfire.c
index 251c97f..f2ac075 100644
--- a/firmware/target/coldfire/system-coldfire.c
+++ b/firmware/target/coldfire/system-coldfire.c
@@ -324,5 +324,8 @@
 /* Set DATAINCONTROL without disturbing FIFO reset state */
 void coldfire_set_dataincontrol(unsigned long value)
 {
+    /* Have to be atomic against recording stop initiated by DMA1 */
+    int level = set_irq_level(DMA_IRQ_LEVEL);
     DATAINCONTROL = (DATAINCONTROL & (1 << 9)) | value;
+    set_irq_level(level);
 }
diff --git a/firmware/target/coldfire/system-target.h b/firmware/target/coldfire/system-target.h
index 31947a2..97d096f 100644
--- a/firmware/target/coldfire/system-target.h
+++ b/firmware/target/coldfire/system-target.h
@@ -57,7 +57,16 @@
     return m;
 }
 
-#define HIGHEST_IRQ_LEVEL (7<<8)
+/* ColdFire IRQ Levels/Priorities in Rockbox summary:
+ * DMA0   - level 6, priority 0 (playback)
+ * DMA1   - level 6, priority 1 (recording)
+ * TIMER1 - level 4, priority 0 (timers)
+ * TIMER0 - level 3, priority 0 (ticks)
+ * GPI0   - level 3, priority 0 (pcf50606 PMU, secondary controller)
+ */
+#define HIGHEST_IRQ_LEVEL   (5<<8) /* Disable all but DMA and higher */
+#define DMA_IRQ_LEVEL       (6<<8) /* Disable DMA and lower */
+#define DISABLE_INTERRUPTS  (7<<8) /* Disable all but NMIs */
 static inline int set_irq_level(int level)
 {
     int oldlevel;