--- empeg_audio3_orig.c	Sun Oct 21 20:48:28 2001
+++ empeg_audio3.c	Wed Nov 28 13:21:19 2001
@@ -112,6 +112,16 @@
 #define AUDIO_NOOF_BUFFERS		8	/* Number of audio buffers */
 #define AUDIO_BUFFER_SIZE		4608	/* User buffer chunk size */
 
+/* Audio overlay specific variables */
+#define AUDIO_OVERLAY_BUFFERS				(16)
+#define MAX_FREE_OVERLAY_BUFFERS			(AUDIO_OVERLAY_BUFFERS - 2)
+#define AUDIO_OVERLAY_BG_VOLUME_FADED		(0x00004000)
+#define AUDIO_OVERLAY_BG_VOLUME_MAX			(0x00010000)
+#define AUDIO_OVERLAY_BG_VOLUME_FADESTEP	(0x00000AAA)
+
+int audio_overlay_bg_volume = AUDIO_OVERLAY_BG_VOLUME_MAX;
+signed short audio_zero_sample[ AUDIO_BUFFER_SIZE/2 ] = { 0 };
+
 /* Number of audio buffers that can be in use at any one time. This is
    two less since the inactive two are actually still being used by
    DMA while they look like being free. */
@@ -138,6 +148,12 @@
 	int  count;
 } audio_buf;
 
+struct
+{
+	audio_buf *buffers;
+	int used,free,head,tail;
+} audio_overlay;
+
 typedef struct
 {
 	/* Buffers */
@@ -217,6 +233,14 @@
 
 	/* Blank everything to start with */
 	memset(dev, 0, sizeof(audio_dev));
+
+	// initialize audio overlay buffers
+	if ((audio_overlay.buffers=kmalloc(sizeof(audio_buf)*AUDIO_OVERLAY_BUFFERS,GFP_KERNEL))==NULL)
+		printk(AUDIO_NAME ": can't get memory for audio overlay buffers");
+	for(i = 0; i < AUDIO_OVERLAY_BUFFERS; i++)
+		audio_overlay.buffers[i].count = 0;
+	audio_overlay.head = audio_overlay.tail = audio_overlay.used = 0;
+	audio_overlay.free = MAX_FREE_OVERLAY_BUFFERS;
 	
 	/* Allocate buffers */
 	if ((dev->buffers = kmalloc(sizeof(audio_buf) * AUDIO_NOOF_BUFFERS,
@@ -323,6 +347,41 @@
 		return 0;
 	}
 
+	if( (file->f_flags & O_SYNC) )
+	{
+		if (audio_overlay.free==0) {
+		    struct wait_queue wait = { current, NULL };
+	
+		    add_wait_queue(&dev->waitq, &wait);
+		    current->state = TASK_INTERRUPTIBLE;
+		    while (audio_overlay.free == 0) {
+			schedule();
+		    }
+		    current->state = TASK_RUNNING;
+		    remove_wait_queue(&dev->waitq, &wait);
+		}
+		
+		// fill data to overlay buffers instead
+		while( count > 0 && audio_overlay.free > 0 )
+		{
+			unsigned long flags;
+			save_flags_cli(flags);
+			audio_overlay.free--;
+			restore_flags(flags);
+
+			copy_from_user( audio_overlay.buffers[ audio_overlay.head++ ].data, buffer, AUDIO_BUFFER_SIZE );
+			if( audio_overlay.head == AUDIO_OVERLAY_BUFFERS )
+				audio_overlay.head = 0;
+			total += AUDIO_BUFFER_SIZE;
+			count -= AUDIO_BUFFER_SIZE;
+			
+			save_flags_cli(flags);
+			audio_overlay.used++;
+			restore_flags(flags);
+		}
+		return total;
+	}
+
 	/* Any space left? (No need to disable IRQs: we're just checking for a
 	   full buffer condition) */
 	/* This version doesn't have races, see p209 of Linux Device Drivers */
@@ -561,6 +620,215 @@
 	dsp_write(Y_sinusMode, 0x89a);
 }
 
+// consumes 1.7% of CPU time on 220MHz SA1100 when processing 176400 bytes/second
+void audio_mix( signed short* pDstSample, signed short* pSrcSample )
+{
+	__asm__ (
+		"stmfd	r13!, {r0-r12,r14}\n\t"
+		
+		// initialize registers
+		"ldr	r1, %0\n\t"				// r1 = audio_overlay_bg_volume
+		"ldr	r4, %1\n\t"				// r4 = pDstSample
+		"ldr	r5, %2\n\t"				// r5 = pSrcSample
+		"mov	r6, #2304\n\t"			// r6 = loop counter
+		"sub	r6, r6, #1\n\t"			// r6 -= 1
+		"mov	r7, #65280\n\t"			// r7 = ...
+		"add	r7, r7, #255\n\t"		// r7 = 0xFFFF
+		"mov	r8, #32512\n\t"			// r8 = ...
+		"add	r8, r8, #255\n\t"		// r8 = 32767
+
+		".MixLoopStart:\n\t"
+
+		// scale destination sample down by fixed-point multiplier
+		"ldrsh	r0, [r4]\n\t"			// r0 = *pDstSample
+		"mov	r0, r0, lsl#16\n\t"		// r0 <<= 16
+		"smull  r2, r3, r1, r0\n\t"		// r2 = lo(r1 * r0), r3 = hi(r1 * r0)
+		"adc	r2, r2, #32768\n\t"		// r2 += 32768 + carry
+		"mov	r2, r2, lsr#16\n\t"		// r2 >>= 16
+		"and	r3, r3, r7\n\t"			// r3 &= r7
+		"orr	r0, r2, r3, lsl#16\n\t"	// r0 = r2 | (r3 << 16)
+		
+		// add source sample to scaled destination sample
+		"ldrsh	r2, [r5]\n\t"			// r2 = *pSrcSample
+		"add	r2, r2, r0, asr#16\n\t"	// r2 += (r0 >> 16)
+	
+		// clamp destination sample up
+		"cmp	r2, r8\n\t"				// if( r2 > 32767 )
+		"movgt	r2, r8\n\t"				//     r2 = 32767
+
+		// clamp destination sample down
+		"cmn	r2, r8\n\t"				// if( r2 < -32768 )
+		"mvnlt	r2, r8\n\t"				//     r2 = -32768
+
+		// put processed sample back to memory
+		"strh	r2, [r4]\n\t"			// *pDstSample = r2
+
+		"add	r4, r4, #2\n\t"			// r4 += 2 (get next dst sample pointer)
+		"add	r5, r5, #2\n\t"			// r5 += 2 (get next src sample pointer)
+
+		// loop back
+		"subs	r6, r6, #1\n\t"			// r6 -= 1
+		"bpl	.MixLoopStart\n\t"
+
+		"ldmfd	r13!, {r0-r12,r14}\n\t"
+		: // no outputs
+		: "m" (audio_overlay_bg_volume), "m" (pDstSample), "m" (pSrcSample) );
+}
+
+int audio_overlay_in_use()
+{
+	return audio_overlay.used > 0;
+}
+
+// copies audio data to dma; if audio buffer and overlay buffer both has
+// data, it mixes them together and outputs to dma; if only one has data, it
+// outputs that into dma and if both are empty, it outputs zero sample to dma
+// parameter: dma_register == 0 == DBSA0/DBTA0, dma_register == 1 == DBSB0/DBTB0
+void audio_data_to_dma( int dma_register )
+{
+	static int iSwitchToPCM = 0;
+	static int iFadeVolumeUp = 0;
+	static int iStoredVolume = 0;
+	int iPreviousOverlayUsed = audio_overlay.used;
+	audio_dev *dev=&audio[0];
+
+	if( empeg_mixer_get_input() != 1 ) // INPUT_PCM
+	{
+		if( audio_overlay.used > 0 && !iSwitchToPCM )
+		{
+			if( !iFadeVolumeUp )
+				iStoredVolume = empeg_mixer_getvolume();
+
+			// start fading volume towards 0
+			iSwitchToPCM = 1;
+			iFadeVolumeUp = 0;
+		}
+		// output regular PCM data (should be silent as non-PCM output is active)
+		if( dev->used > 0 )
+		{
+			if( dma_register )
+		    	DBSB0=(unsigned char*)virt_to_phys(dev->buffers[dev->tail].data);
+		    else
+		    	DBSA0=(unsigned char*)virt_to_phys(dev->buffers[dev->tail].data);
+			if (++dev->tail==AUDIO_NOOF_BUFFERS) dev->tail=0;
+			dev->used--; dev->free++;
+		}
+		else
+		{
+			if( dma_register )
+				DBSB0=(unsigned char*)_ZeroMem;
+			else
+				DBSA0=(unsigned char*)_ZeroMem;
+		}
+	}
+	else
+	{
+		// dsp mode is PCM
+		if( dev->used == 0 )
+		{
+			if( audio_overlay.used == 0 )
+			{
+				if( dma_register )
+					DBSB0=(unsigned char*)_ZeroMem;
+				else
+					DBSA0=(unsigned char*)_ZeroMem;
+			}
+			else
+			{
+				audio_overlay_bg_volume = AUDIO_OVERLAY_BG_VOLUME_FADED;
+				if( dma_register )
+		    		DBSB0=(unsigned char*)virt_to_phys(audio_overlay.buffers[audio_overlay.tail].data);
+		    	else
+		    		DBSA0=(unsigned char*)virt_to_phys(audio_overlay.buffers[audio_overlay.tail].data);
+				if( ++audio_overlay.tail == AUDIO_OVERLAY_BUFFERS ) audio_overlay.tail = 0;
+				audio_overlay.used--; audio_overlay.free++;
+			}
+		}
+		else
+		{
+			if( audio_overlay.used > 0 )
+			{
+				if( audio_overlay_bg_volume > AUDIO_OVERLAY_BG_VOLUME_FADED )
+				{
+					audio_overlay_bg_volume -= AUDIO_OVERLAY_BG_VOLUME_FADESTEP;
+					if( audio_overlay_bg_volume < AUDIO_OVERLAY_BG_VOLUME_FADED )
+						audio_overlay_bg_volume = AUDIO_OVERLAY_BG_VOLUME_FADED;
+					audio_mix( (signed short*)dev->buffers[ dev->tail ].data, &audio_zero_sample[0] );
+				}
+				else
+				{
+					audio_mix( (signed short*)dev->buffers[ dev->tail ].data, 
+							   (signed short*)audio_overlay.buffers[ audio_overlay.tail ].data );
+					if( ++audio_overlay.tail == AUDIO_OVERLAY_BUFFERS ) audio_overlay.tail = 0;
+					audio_overlay.used--; audio_overlay.free++;
+				}
+			}
+			else
+			{
+				if( audio_overlay_bg_volume < AUDIO_OVERLAY_BG_VOLUME_MAX )
+				{
+					audio_overlay_bg_volume += AUDIO_OVERLAY_BG_VOLUME_FADESTEP;
+					if( audio_overlay_bg_volume > AUDIO_OVERLAY_BG_VOLUME_MAX )
+						audio_overlay_bg_volume = AUDIO_OVERLAY_BG_VOLUME_MAX;
+					audio_mix( (signed short*)dev->buffers[ dev->tail ].data, &audio_zero_sample[0] );
+				}
+			}
+			if( dma_register )
+		    	DBSB0=(unsigned char*)virt_to_phys(dev->buffers[dev->tail].data);
+		    else
+		    	DBSA0=(unsigned char*)virt_to_phys(dev->buffers[dev->tail].data);
+			if (++dev->tail==AUDIO_NOOF_BUFFERS) dev->tail=0;
+			dev->used--; dev->free++;
+		}
+
+		// if all overlay buffers are played and user DSP mode is different, do a change		
+		if( audio_overlay.used == 0 && iPreviousOverlayUsed > 0 &&
+		    empeg_mixer_get_input() != empeg_mixer_get_user_input() )
+		{
+			if( !iFadeVolumeUp )
+			{
+				iStoredVolume = empeg_mixer_getvolume();
+				empeg_mixer_setvolume( 0 );
+				empeg_mixer_select_input( empeg_mixer_get_user_input() );
+				// fade volume back to stored volume
+				iFadeVolumeUp = 1;
+			}
+		}
+	}
+
+	if( dma_register )
+		DBTB0=AUDIO_BUFFER_SIZE;
+	else
+		DBTA0=AUDIO_BUFFER_SIZE;
+
+	if( iSwitchToPCM )
+	{
+		// fade volume towards 0...
+		int iVol = empeg_mixer_getvolume();
+		iVol -= 7; // takes roughly 0.3sec to fade from 100 to 0
+		if( iVol < 0 )
+		{
+			empeg_mixer_select_input( 1 ); // INPUT_PCM
+			empeg_mixer_setvolume( iStoredVolume );
+			iSwitchToPCM = 0;
+		}
+		else
+			empeg_mixer_setvolume( iVol );
+	}
+	else if( iFadeVolumeUp )
+	{
+		// fade volume towards stored volume...
+		int iVol = empeg_mixer_getvolume();
+		iVol += 7; // takes roughly 0.3sec to fade from 0 to 100
+		if( iVol > iStoredVolume )
+		{
+			iVol = iStoredVolume;
+			iFadeVolumeUp = 0;
+		}
+		empeg_mixer_setvolume( iVol );
+	}
+}
+
 /*                      
  * Interrupt processing 
  */
@@ -583,44 +851,18 @@
         if (dofirst== 0) {
 		ClrDCSR0 = DCSR_DONEB;
 		
-		/* Any data to get? */
-		if (dev->used == 0) {
-			DBSA0 = (unsigned char *) _ZeroMem;
-			DBTA0 = AUDIO_BUFFER_SIZE;
-
-
-			/* If we've underrun, take note */
-			if (dev->good_data) {
-				dev->good_data = 0;
-				dev->stats.user_underruns++;
-			}
-		}
-		else {
-		        DBSA0 =	(unsigned char *)
-				virt_to_phys(dev->buffers[dev->tail].data);
-			DBTA0 = AUDIO_BUFFER_SIZE;
-			if (++dev->tail == AUDIO_NOOF_BUFFERS) dev->tail = 0;
-			dev->used--;
-			dev->free++;
+		/* If we've underrun, take note */
+		if( dev->used == 0 && dev->good_data )
+		{
+			dev->good_data = 0;
+			dev->stats.user_underruns++;
 		}
+		audio_data_to_dma( 0 );
 		
 		if (!(status & DCSR_STRTB)) {
 			/* Filling both buffers: possible IRQ underrun */
 			dev->stats.irq_underruns++;
-
-			if (dev->used == 0) {
-				DBSB0 = (unsigned char *) _ZeroMem;
-				DBTB0 = AUDIO_BUFFER_SIZE;
-			}
-			else {
-				DBSB0 = (unsigned char *) virt_to_phys(
-					dev->buffers[dev->tail].data);
-				DBTB0 = AUDIO_BUFFER_SIZE;
-				if (++dev->tail == AUDIO_NOOF_BUFFERS)
-					dev->tail = 0;
-				dev->used--;
-				dev->free++;
-			}
+			audio_data_to_dma( 1 );
 			
 			/* Start both channels */
 			SetDCSR0 =
@@ -633,44 +875,18 @@
 	else {
 		ClrDCSR0 = DCSR_DONEA;
 
-		/* Any data to get? */
-		if (dev->used == 0) {
-			DBSB0 = (unsigned char *) _ZeroMem;
-			DBTB0 = AUDIO_BUFFER_SIZE;
-
-			/* If we've underrun, take note */
-			if (dev->good_data) {
-				dev->good_data = 0;
-				dev->stats.user_underruns++;
-			}
-		}
-		else {
-			DBSB0 = (unsigned char *)
-				virt_to_phys(dev->buffers[dev->tail].data);
-			DBTB0 = AUDIO_BUFFER_SIZE;
-			if (++dev->tail == AUDIO_NOOF_BUFFERS)
-				dev->tail=0;
-			dev->used--;
-			dev->free++;
+		/* If we've underrun, take note */
+		if( dev->used == 0 && dev->good_data )
+		{
+			dev->good_data = 0;
+			dev->stats.user_underruns++;
 		}
+		audio_data_to_dma( 1 );
 		
 		if (!(status & DCSR_STRTA)) {
 			/* Filling both buffers: possible IRQ underrun */
 			dev->stats.irq_underruns++;
-
-			if (dev->used == 0) {
-				DBSA0 = (unsigned char *) _ZeroMem;
-				DBTA0 = AUDIO_BUFFER_SIZE;
-			}
-			else {
-				DBSA0 = (unsigned char*) virt_to_phys(
-					dev->buffers[dev->tail].data);
-				DBTA0 = AUDIO_BUFFER_SIZE;
-				if (++dev->tail == AUDIO_NOOF_BUFFERS)
-					dev->tail=0;
-				dev->used--;
-				dev->free++;
-			}
+			audio_data_to_dma( 0 );
 
 			/* Start both channels */
 			SetDCSR0 =
--- empeg_audio3_orig.h	Sun Oct 21 20:48:37 2001
+++ empeg_audio3.h	Sun Oct 21 21:14:00 2001
@@ -9,4 +9,6 @@
 int empeg_audio_open(struct inode *inode, struct file *file);
 void empeg_audio_beep_setup(int rate);
 
+int audio_overlay_in_use(void);
+
 #endif
--- empeg_mixer_orig.c	Sun Oct 21 20:48:48 2001
+++ empeg_mixer.c	Mon Oct 22 16:05:14 2001
@@ -66,6 +66,8 @@
 typedef struct
 {
 	int input;
+	int inputInternal;
+	int userInputInternal;
 	unsigned int flags;
 	int volume;
 	int loudness;
@@ -132,9 +134,9 @@
 static int empeg_mixer_ioctl(struct inode *inode, struct file *file,
 			     uint command, ulong arg);
 
-static void empeg_mixer_select_input(int input);
+//static void empeg_mixer_select_input(int input);
 static void empeg_mixer_setloudness(mixer_dev *dev, int level);
-static int empeg_mixer_setvolume(mixer_dev *dev, int vol);
+//static int empeg_mixer_setvolume(mixer_dev *dev, int vol);
 static int empeg_mixer_getdb(mixer_dev *dev);
 static void empeg_mixer_inflict_flags(mixer_dev *dev);
 static void empeg_mixer_setbalance(mixer_dev *dev, int balance);
@@ -195,7 +197,7 @@
 	empeg_mixer_mute(1);
 
 	/* Set volume */
-	empeg_mixer_setvolume(dev,90);
+	empeg_mixer_setvolume(90);
 
 	/* try doing this last thing */
 	empeg_mixer_select_input(INPUT_PCM);
@@ -399,7 +401,7 @@
 		vol_out = ((vol_in & 0xFF) + ((vol_in >> 8) & 0xFF))>>1;
 		if (vol_out < 0 || vol_out > 100)
 			return -EINVAL;
-		empeg_mixer_setvolume(dev, vol_out);
+		empeg_mixer_setvolume(vol_out);
 		vol_out = dev->volume;
 		vol_out = (vol_out & 0xFF) + ((vol_out << 8));
 		put_user_ret(vol_out, (int *)arg, -EFAULT);
@@ -458,24 +460,27 @@
 #endif	
 		if (source & SOUND_MASK_PCM)
 		{
-			empeg_mixer_select_input(INPUT_PCM);
+			dev->userInputInternal = INPUT_PCM;
 			dev->input = SOUND_MASK_PCM;
 		}
 		else if (source & SOUND_MASK_RADIO)
 		{
-			empeg_mixer_select_input(INPUT_RADIO_FM);
+			dev->userInputInternal = INPUT_RADIO_FM;
 			dev->input = SOUND_MASK_RADIO;
 		}
 		else if (source & SOUND_MASK_LINE1)
 		{
-			empeg_mixer_select_input(INPUT_RADIO_AM);
+			dev->userInputInternal = INPUT_RADIO_AM;
 			dev->input = SOUND_MASK_LINE1;
 		}
 		else if (source & SOUND_MASK_LINE)
 		{
-		        empeg_mixer_select_input(INPUT_AUX);
+			dev->userInputInternal = INPUT_AUX;
 			dev->input = SOUND_MASK_LINE;
 		}
+		if( dev->userInputInternal != dev->inputInternal && !audio_overlay_in_use() )
+			empeg_mixer_select_input( dev->userInputInternal );
+		
 		put_user_ret(dev->input, (int *)arg, -EFAULT);
 		return 0;
 	}
@@ -850,7 +855,17 @@
 #endif
 }
 
-static void empeg_mixer_select_input(int input)
+int empeg_mixer_get_input()
+{
+	return mixer_global.inputInternal;
+}
+
+int empeg_mixer_get_user_input()
+{
+	return mixer_global.userInputInternal;
+}
+
+/*static*/ void empeg_mixer_select_input(int input)
 {
 	static dsp_setup fm_setup[]=
 	{ { 0xfff, 0x5323 },
@@ -898,6 +913,7 @@
 	  { 0,0 } };
 
 	mixer_dev *dev = &mixer_global;
+	dev->inputInternal = input;
 
 	eq_reg_last = radio_sensitivity;
 	dsp_patchmulti(fm_setup, 0xffd, eq_reg_last);
@@ -1093,7 +1109,8 @@
 		/* Wait for a while so that the I2S being clocked into the
 		   DSP by DMA runs the initialisation code: the DSP is cycle-
 		   locked to the incoming bitstream */
-		{ int a=jiffies+(HZ/20); while(jiffies<a); }
+//		{ int a=jiffies+(HZ/20); while(jiffies<a); }
+		{ int a=0; for( a = 0; a < 1000; ++a ) udelay( 50 ); } // jiffies can not be used inside interrupt
 		
 		/* Select mode */
 		dsp_write(X_modpntr,0x0200);
@@ -1129,17 +1146,22 @@
 	}
 }
 
-static int empeg_mixer_setvolume(mixer_dev *dev, int vol)
+/*static*/ int empeg_mixer_setvolume(/*mixer_dev *dev,*/ int vol)
 {
 	dsp_write(Y_VAT, volume_table[vol].vat);
 	dsp_write(Y_VGA, volume_table[vol].vga);
-	dev->volume = vol;
+	mixer_global.volume = vol;	
 
 #if MIXER_DEBUG
 	printk(MIXER_NAME ": volume set %d VAT:%03x VGA:%03x\n",vol,
 	       volume_table[vol].vat,volume_table[vol].vga);
 #endif
 	return vol;
+}
+
+int empeg_mixer_getvolume(void)
+{
+	return mixer_global.volume;
 }
 
 static int empeg_mixer_getdb(mixer_dev *dev)
--- empeg_mixer_orig.h	Sun Oct 21 20:48:52 2001
+++ empeg_mixer.h	Mon Oct 22 16:05:14 2001
@@ -17,4 +17,11 @@
 void empeg_mixer_set_stereo(int on);
 int empeg_mixer_get_multipath(void);
 
+void empeg_mixer_select_input(int input);
+int empeg_mixer_get_input(void);
+int empeg_mixer_get_user_input(void);
+
+int empeg_mixer_setvolume(int vol);
+int empeg_mixer_getvolume(void);
+
 #endif
