/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *  Routines for control of GF1 chip (PCM things)
 *
 *  Input samples must be separated for each voices for GF1 chip.
 *  Input:  L1R1L1R1L1R1L2R2L2R2L2R2 (user space or mmaped DMA buffer)
 *  Output: L1L1L1L2L2L2R1R1R1R2R2R2 (DMA buffer or hidden DMA buffer)
 *  Number in this case means fragment.
 *  InterWave chips supports interleaved DMA, but this feature isn't used in
 *  this code.
 *  
 *  This code emulates autoinit DMA transfer for playback, recording by GF1
 *  chip doesn't support autoinit DMA.
 *
 *  MMAPed access is emulated for OSS, but is a little bit ugly :-(((((
 *
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#define __SND_OSS_COMPAT__
#include "driver.h"
#include "ulaw.h"
#include "gus.h"
#include "gus_tables.h"

/* maximum rate */

#define SND_GF1_PCM_RATE	48000

/* position */

#define SND_POS_NONE		0xffffffff

/* playback flags */

#define SND_GF1_PCM_PFLG_NONE  	0x0000
#define SND_GF1_PCM_PFLG_UP	0x0001	/* trigger up */
#define SND_GF1_PCM_PFLG_DMA	0x0004	/* DMA in progress */
#define SND_GF1_PCM_PFLG_ACTIVE	0x0008	/* voice is playing */

/* record flags */

#define RFLG_NONE		0x0000
#define RFLG_USED		0x0001
#define RFLG_STOP		0x0002

/* some macros */

#define SND_GF1_PCM_VOICES() \
	( gus -> gf1.voice_ranges[ SND_GF1_VOICE_RANGE_PCM ].voices )
#define SND_GF1_PCM_VOICE_MIN()	\
	( gus -> gf1.voice_ranges[ SND_GF1_VOICE_RANGE_PCM ].min )
#define SND_GF1_PCM_VOICE_MAX()	\
	( gus -> gf1.voice_ranges[ SND_GF1_VOICE_RANGE_PCM ].max )
#define SND_GF1_PCM_RVOICES() \
	( gus -> gf1.voice_ranges[ SND_GF1_VOICE_RANGE_PCM ].rvoices )

#if defined( __i386__ ) && 1

#if 0

extern inline void translate_bytes(unsigned char *table,
				   unsigned char *buff,
				   unsigned int count)
{
	__asm__("cld\n"
		"1:\tlodsb\n"
		"\txlatb\n"
		"\tstosb\n"
      "\tloop 1b\n":
      :"b"((long) table), "c"(count), "D"((long) buff), "S"((long) buff)
      :	"bx", "cx", "di", "si", "ax", "memory");
}

#endif

extern inline void divide_bytes(unsigned char *dest1,
				unsigned char *dest2,
				unsigned char *src,
				unsigned int count)
{
	__asm__("cld\n"
		"1:\tlodsw\n"
		"\tstosb\n"
		"\txchgb %%ah,%%al\n"
		"\txchgl %%edi,%%ebx\n"
		"\tstosb\n"
		"\txchgl %%edi,%%ebx\n"
      "\tloop 1b\n":
      :	"c"(count), "S"((long) src), "D"((long) dest1), "b"(dest2)
      :	"bx", "cx", "di", "si", "ax", "memory");
}

extern inline void divide_words(unsigned short *dest1,
				unsigned short *dest2,
				unsigned short *src,
				unsigned int count)
{
	__asm__("cld\n"
		"1:\tlodsw\n"
		"\tstosw\n"
		"\tlodsw\n"
		"\txchgl %%edi,%%ebx\n"
		"\tstosw\n"
		"\txchgl %%edi,%%ebx\n"
      "\tloop 1b\n":
      :	"c"(count), "S"((long) src), "D"((long) dest1), "b"(dest2)
      :	"bx", "cx", "di", "si", "ax", "memory");
}

#else

#if 0

static void translate_bytes(unsigned char *table,
			    unsigned char *buff,
			    unsigned int count)
{
	while (count-- > 0) {
		*buff = table[*buff];
		buff++;
	}
}

#endif

static void divide_bytes(unsigned char *dest1,
			 unsigned char *dest2,
			 unsigned char *src,
			 unsigned int count)
{
	while (count-- > 0) {
		*dest1++ = *src++;
		*dest2++ = *src++;
	}
}

static void divide_words(unsigned short *dest1,
			 unsigned short *dest2,
			 unsigned short *src,
			 unsigned int count)
{
	while (count-- > 0) {
		*(((unsigned short *) dest1)++) = *(((unsigned short *) src)++);
		*(((unsigned short *) dest2)++) = *(((unsigned short *) src)++);
	}
}

#endif

/*
 *  Playback routines 
 */

static void snd_gf1_pcm_translate(unsigned int voices,
				  unsigned char *src,
				  unsigned char *dest,
				  unsigned int pos,
				  unsigned int count,
				  unsigned int total,
				  unsigned int mode,
				  int user_space)
{
	int voice;
	unsigned int pos1;
	unsigned int tmp;
	unsigned int count1;
	unsigned char buffer[512];
	unsigned char *bpos, *dest1, *dest2;

#if 0
	snd_printk("pcm: voices = %i, src=0x%lx, dest=0x%lx, pos=0x%x, count=0x%x, total=0x%x\n", voices, (long) src, (long) dest, pos, count, total);
	if (user_space && verify_area(VERIFY_READ, src, count))
		snd_printk("Verify failed!!!\n");
#endif
	tmp = voices * ((mode & SND_PCM1_MODE_16) ? 2 : 1);
	if ((count % tmp) != 0) {
		snd_printd("sound: GF1 PCM problem - unaligned count (voices = %i, count = %i)\n", voices, count);
		return;
	}
	if ((pos % tmp) != 0) {
		snd_printd("sound: GF1 PCM problem - unaligned copy (voices = %i, pos = %i)\n", voices, pos);
		return;
	}
	pos /= voices;
	total /= voices;
#if 0
	printk("pos = 0x%x, total = 0x%x\n", pos, total);
#endif
	while (count > 0) {
		if (user_space) {
			count1 = count > sizeof(buffer) ? sizeof(buffer) : count;
			copy_from_user(buffer, src, count1);
			src += count1;
			bpos = buffer;
			count -= count1;
		} else {
			count1 = count;
			bpos = src;
			count = 0;
		}

		/*
		 *  Next part strongly needs optimalization...
		 *  Optimalization for one and two voices is done (not for uLaw)...
		 *  I don't have time for this... If you want - try rewrite this part...
		 */
		if (mode & SND_PCM1_MODE_16) {
			if (voices == 1) {
				memcpy(dest + pos, bpos, count1);
				pos += count1;
			} else if (voices == 2) {
				divide_words((unsigned short *) (dest + pos),
				 (unsigned short *) (dest + pos + total),
				   (unsigned short *) bpos, count1 >> 2);
				pos += count1 >> 1;
			} else {
				count1 >>= 1;
				while (count1 > 0) {
					for (voice = 0, pos1 = pos; voice < voices; voice++, pos1 += total)
						*(unsigned short *) &dest[pos1] = *(((unsigned short *) bpos)++);
					pos += sizeof(unsigned short);
					count1 -= voices;
				}
			}
		} else {
			if (mode & SND_PCM1_MODE_ULAW) {
				if (voices == 1) {
					dest1 = dest + pos;
					pos += count1;
					while (count1-- > 0)
						*dest1++ = snd_ulaw_dsp[*bpos++];
				} else if (voices == 2) {
					dest1 = dest + pos;
					dest2 = dest1 + total;
					pos += count1;
					count1 >>= 1;
					while (count1-- > 0) {
						*dest1++ = snd_ulaw_dsp[*bpos++];
						*dest2++ = snd_ulaw_dsp[*bpos++];
					}
				} else {
					while (count1 > 0) {
						for (voice = 0, pos1 = pos; voice < voices; voice++, pos1 += total)
							dest[pos1] = snd_ulaw_dsp[*bpos++];
						pos++;
						count1 -= voices;
					}
				}
			} else {
				if (voices == 1) {
					memcpy(dest + pos, bpos, count1);
					pos += count1;
				} else if (voices == 2) {
					pos += count1;
					divide_bytes(dest + pos, dest + pos + total, bpos, count1 >> 1);
				} else {
					while (count1 > 0) {
						for (voice = 0, pos1 = pos; voice < voices; voice++, pos1 += total)
							dest[pos1] = *bpos++;
						pos++;
						count1 -= voices;
					}
				}
			}
		}
	}
#if 0
	snd_printk("translate end\n");
#endif
}

static void snd_gf1_pcm_block_change(snd_pcm1_t * pcm1, snd_gus_card_t * gus, unsigned int offset, unsigned int count)
{
	unsigned int cmd;

#if 0
	snd_printk("block change - offset = 0x%x, count = 0x%x\n", offset, count);
#endif
	if (offset & 31) {
		count += count & 31;
		offset &= ~31;
	}
	if (count < 32)
		count = 32;
	cmd = SND_GF1_DMA_IRQ;
	if (pcm1->playback.mode & SND_PCM1_MODE_U)
		cmd |= SND_GF1_DMA_UNSIGNED;
	if (pcm1->playback.mode & SND_PCM1_MODE_16)
		cmd |= SND_GF1_DMA_16BIT;
	snd_gf1_dma_transfer_block(gus,
				   gus->gf1.pcm_memory + offset,
				   pcm1->playback.buffer + offset,
				   count,
				   cmd);
}

static void snd_gf1_pcm_mmap_change(snd_pcm1_t * pcm1,
				    snd_gus_card_t * gus,
				    unsigned int offset,
				    unsigned int poffset,
				    unsigned int count)
{
	unsigned int cmd;

#if 0
	snd_printk("block change - buffer = 0x%lx, offset = 0x%x, poffset = 0x%x, count = 0x%x\n", (long) gus->gf1.pcm_buffer, offset, poffset, count);
#endif
	cmd = SND_GF1_DMA_IRQ;
	if (pcm1->playback.mode & SND_PCM1_MODE_U)
		cmd |= SND_GF1_DMA_UNSIGNED;
	if (pcm1->playback.mode & SND_PCM1_MODE_16)
		cmd |= SND_GF1_DMA_16BIT;
	snd_gf1_dma_transfer_block(gus,
				   gus->gf1.pcm_memory + offset,
				   gus->gf1.pcm_buffer + poffset,
				   count,
				   cmd);
}

static unsigned short snd_gf1_pcm_volume(snd_gus_card_t * gus,
					 int vol, int pan)
{
	unsigned long flags;

	snd_spin_lock(gus, pcm_volume_level, &flags);
	if (vol > 127)
		vol = 127;
	vol = SND_GF1_VOLUME(vol);
	if (pan < 64)		/* left */
		vol += gus->gf1.pcm_volume_level_left;
	else
		vol += gus->gf1.pcm_volume_level_right;
	if (vol > SND_GF1_MAX_VOLUME)
		vol = SND_GF1_MAX_VOLUME;
	vol = SND_GF1_MAX_VOLUME - vol;
	snd_spin_unlock(gus, pcm_volume_level, &flags);
	return vol;
}

static void snd_gf1_pcm_trigger_up(snd_pcm1_t * pcm1, snd_gus_card_t * gus)
{
	unsigned long flags;
	unsigned char voice_ctrl, ramp_ctrl;
	unsigned short rate;
	unsigned int tmp, curr, begin, end;
	unsigned short vol;
	unsigned char pan;
	unsigned int voice;

	snd_spin_lock(gus, playback, &flags);
	if (gus->gf1.pcm_pflags & SND_GF1_PCM_PFLG_ACTIVE) {
		snd_spin_unlock(gus, playback, &flags);
		return;
	}
	gus->gf1.pcm_pflags |= SND_GF1_PCM_PFLG_ACTIVE;
	snd_spin_unlock(gus, playback, &flags);
	rate = snd_gf1_translate_freq(gus, pcm1->playback.real_rate << 1);
	if (gus->gf1.pcm_pos != SND_POS_NONE) {
		voice_ctrl = gus->gf1.pcm_voice_ctrl;
		/* enable RAMP IRQ */
		ramp_ctrl = gus->gf1.pcm_ramp_ctrl | 0x20;
	} else {
		/* enable WAVE IRQ */
		voice_ctrl = pcm1->playback.mode & SND_PCM1_MODE_16 ? 0x24 : 0x20;
		/* enable RAMP IRQ + rollover */
		ramp_ctrl = 0x24;
	}
	tmp = SND_GF1_PCM_RVOICES();
	for (voice = 0; voice < SND_GF1_PCM_VOICES(); voice++) {
		begin = gus->gf1.pcm_memory + voice * (pcm1->playback.used_size / tmp);
		curr = begin + (gus->gf1.pcm_bpos * gus->gf1.pcm_block_size) / tmp;
		end = curr + (gus->gf1.pcm_block_size / tmp);
		if (gus->gf1.pcm_pos != SND_POS_NONE)	/* stored position */
			curr = begin + gus->gf1.pcm_pos;
		end -= pcm1->playback.mode & SND_PCM1_MODE_16 ? 2 : 1;
#if 0
		snd_printk("init: curr=0x%x, begin=0x%x, end=0x%x, ctrl=0x%x, ramp=0x%x, rate=0x%x\n", curr, begin, end, voice_ctrl, ramp_ctrl, rate);
#endif
		if (pcm1->playback.mode & SND_PCM1_MODE_MULTI) {
			vol = snd_gf1_pcm_volume(gus, gus->gf1.pcm_volume[voice], pan = gus->gf1.pcm_pan[voice]);
		} else {
			if (pcm1->playback.voices == 2)
				pan = !voice ? 8 : 128 - 9;
			else
				pan = 64;
			vol = snd_gf1_pcm_volume(gus, 128, pan);
		}
		pan >>= 3;
		if (pan > 15)
			pan = 15;
		snd_spin_lock(gus, reg, &flags);
		snd_gf1_select_voice(gus, voice + SND_GF1_PCM_VOICE_MIN());
		snd_gf1_write8(gus, SND_GF1_VB_PAN, pan);
		snd_gf1_write16(gus, SND_GF1_VW_FREQUENCY, rate);
		snd_gf1_write_addr(gus, SND_GF1_VA_START, begin << 4, voice_ctrl & 4);
		snd_gf1_write_addr(gus, SND_GF1_VA_END, end << 4, voice_ctrl & 4);
		snd_gf1_write_addr(gus, SND_GF1_VA_CURRENT, curr << 4, voice_ctrl & 4);
		snd_gf1_write16(gus, SND_GF1_VW_VOLUME, SND_GF1_MIN_VOLUME << 4);
		snd_gf1_write8(gus, SND_GF1_VB_VOLUME_RATE, 0x2f);
		snd_gf1_write8(gus, SND_GF1_VB_VOLUME_START, SND_GF1_MIN_OFFSET);
		snd_gf1_write8(gus, SND_GF1_VB_VOLUME_END, vol >> 4);	/* ramp end */
		snd_gf1_write8(gus, SND_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
		if (!gus->gf1.enh_mode) {
			snd_gf1_delay(gus);
			snd_gf1_write8(gus, SND_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
		}
		snd_spin_unlock(gus, reg, &flags);
	}
	snd_spin_lock(gus, reg, &flags);
	for (voice = SND_GF1_PCM_VOICE_MIN(); voice <= SND_GF1_PCM_VOICE_MAX(); voice++) {
		snd_gf1_select_voice(gus, voice);
		if (gus->gf1.enh_mode)
			snd_gf1_write8(gus, SND_GF1_VB_MODE, 0x00);	/* deactivate voice */
		snd_gf1_write8(gus, SND_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
		voice_ctrl &= ~0x20;
	}
	voice_ctrl |= 0x20;
	if (!gus->gf1.enh_mode) {
		snd_gf1_delay(gus);
		for (voice = SND_GF1_PCM_VOICE_MIN(); voice <= SND_GF1_PCM_VOICE_MAX(); voice++) {
			snd_gf1_select_voice(gus, voice);
			snd_gf1_write8(gus, SND_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
			voice_ctrl &= ~0x20;	/* disable IRQ for next voice */
		}
	}
	snd_spin_unlock(gus, reg, &flags);
#if 0
	for (voice = SND_GF1_PCM_VOICE_MIN(); voice <= SND_GF1_PCM_VOICE_MAX(); voice++) {
		CLI(&flags);
		snd_gf1_select_voice(gus, voice);
		snd_gf1_print_voice_registers(gus);
		STI(&flags);
	}
#endif
}

static void snd_gf1_pcm_interrupt_wave(snd_gus_card_t * gus, int voice)
{
	unsigned long flags;
	unsigned char voice_ctrl, ramp_ctrl;
	int idx;
	unsigned int end, step;
	snd_pcm1_t *pcm1;

	pcm1 = (snd_pcm1_t *) gus->pcm->private_data;
	if (!pcm1 || voice != SND_GF1_PCM_VOICE_MIN() || !(gus->gf1.pcm_pflags & SND_GF1_PCM_PFLG_ACTIVE)) {
		snd_printd("ultra_gf1_pcm: unknown wave irq?\n");
		snd_gf1_smart_stop_voice(gus, voice);
		return;
	}
	snd_spin_lock(gus, reg, &flags);
	voice_ctrl = snd_gf1_read8(gus, SND_GF1_VB_ADDRESS_CONTROL) & ~0x8b;
	ramp_ctrl = snd_gf1_read8(gus, SND_GF1_VB_VOLUME_CONTROL) & ~0x84;
#if 0
	printk("irq: bpos = %i\n", gus->gf1.pcm_bpos);
#endif
	gus->gf1.pcm_bpos++;
	gus->gf1.pcm_bpos %= gus->gf1.pcm_blocks;
	snd_spin_unlock(gus, reg, &flags);

#if 0
	snd_spin_lock(gus, reg, &flags);
	for (idx = SND_GF1_PCM_VOICE_MIN(); idx <= SND_GF1_PCM_VOICE_MAX(); idx++) {
		snd_gf1_select_voice(gus, idx);
		snd_printk("** curr=0x%x, begin=0x%x, end=0x%x, ctrl=0x%x, ramp=0x%x\n",
			   end = snd_gf1_read_addr(gus, SND_GF1_VA_CURRENT, voice_ctrl & 4),
		snd_gf1_read_addr(gus, SND_GF1_VA_START, voice_ctrl & 4),
		  snd_gf1_read_addr(gus, SND_GF1_VA_END, voice_ctrl & 4),
			   snd_gf1_read8(gus, SND_GF1_VB_ADDRESS_CONTROL),
			   snd_gf1_read8(gus, SND_GF1_VB_VOLUME_CONTROL));
	}
	snd_gf1_select_voice(gus, voice);
	snd_spin_unlock(gus, reg, &flags);
#endif

	if (gus->gf1.pcm_bpos + 1 >= gus->gf1.pcm_blocks) {	/* last block? */
		voice_ctrl |= 0x08;	/* enable loop */
	} else {
		ramp_ctrl |= 0x04;	/* enable rollover */
	}
	end = gus->gf1.pcm_memory +
	    (((gus->gf1.pcm_bpos + 1) * gus->gf1.pcm_block_size) / SND_GF1_PCM_RVOICES());
	end -= voice_ctrl & 4 ? 2 : 1;
	step = pcm1->playback.used_size / pcm1->playback.voices;
	snd_spin_lock(gus, reg, &flags);
	voice_ctrl |= 0x20;
	for (idx = SND_GF1_PCM_VOICE_MIN(); idx <= SND_GF1_PCM_VOICE_MAX(); idx++, end += step) {
		snd_gf1_select_voice(gus, idx);
		snd_gf1_write_addr(gus, SND_GF1_VA_END, end << 4, voice_ctrl & 4);
		snd_gf1_write8(gus, SND_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
		snd_gf1_write8(gus, SND_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
#if 0
		printk("++ end=0x%x ctrl=0x%x ramp=0x%x\n", end, voice_ctrl, ramp_ctrl);
#endif
		voice_ctrl &= ~0x20;
	}
	if (!gus->gf1.enh_mode) {
		snd_gf1_delay(gus);
		voice_ctrl |= 0x20;
		for (idx = SND_GF1_PCM_VOICE_MIN(); idx <= SND_GF1_PCM_VOICE_MAX(); idx++) {
			snd_gf1_select_voice(gus, idx);
			snd_gf1_write8(gus, SND_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
			snd_gf1_write8(gus, SND_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
			voice_ctrl &= ~0x20;
		}
	}
	snd_gf1_select_voice(gus, voice);	/* return back voice */
	snd_spin_unlock(gus, reg, &flags);


	if (pcm1->playback.flags & SND_PCM1_FLG_MMAP) {
		unsigned int mpos, count, pstep;

		if ((gus->gf1.pcm_bpos & gus->gf1.pcm_mmap_mask) == 0)
			pcm1->playback.ack(pcm1);

		mpos = ((gus->gf1.pcm_bpos + 1) % gus->gf1.pcm_blocks) * gus->gf1.pcm_block_size;
#if 1
		snd_gf1_pcm_translate(pcm1->playback.voices,
				      pcm1->playback.buffer + mpos,
				      gus->gf1.pcm_buffer,
				      0,
				      gus->gf1.pcm_block_size,
				      PAGE_SIZE,
				      pcm1->playback.mode,
				      0);
#else
		memset(gus->gf1.pcm_buffer, 0, PAGE_SIZE);
#endif
		mpos /= pcm1->playback.voices;
		count = gus->gf1.pcm_block_size / pcm1->playback.voices;
		step = pcm1->playback.used_size / pcm1->playback.voices;
		pstep = PAGE_SIZE / pcm1->playback.voices;
		for (idx = 0; idx < SND_GF1_PCM_VOICES(); idx++)
			snd_gf1_pcm_mmap_change(pcm1, gus, mpos + (idx * step), idx * pstep, count);
	} else {
		pcm1->playback.ack(pcm1);	/* ack to high level */
	}
}

static void snd_gf1_pcm_interrupt_volume(snd_gus_card_t * gus, int voice)
{
	unsigned short vol;
	unsigned char pan;
	int cvoice;
	snd_pcm1_t *pcm1;

	/* stop ramp, but leave rollover bit untouched */
	snd_gf1_i_ctrl_stop(gus, SND_GF1_VB_VOLUME_CONTROL);
	/* are we active? */
	if (!(gus->gf1.pcm_pflags & SND_GF1_PCM_PFLG_ACTIVE))
		return;
	/* load real volume - better precision */
	cvoice = voice - SND_GF1_PCM_VOICE_MIN();
	pcm1 = (snd_pcm1_t *) gus->pcm->private_data;
	if (pcm1->playback.mode & SND_PCM1_MODE_MULTI) {
		vol = snd_gf1_pcm_volume(gus, gus->gf1.pcm_volume[cvoice], pan = gus->gf1.pcm_pan[cvoice]);
	} else {
		if (pcm1->playback.voices == 2)
			pan = !cvoice ? 16 : 128 - 16;
		else
			pan = 64;
		vol = snd_gf1_pcm_volume(gus, 128, pan);
	}
	snd_gf1_i_write16(gus, SND_GF1_VW_VOLUME, vol << 4);
}

static void snd_gf1_pcm_voices_change_start(snd_gus_card_t * gus)
{
	unsigned long flags;
	unsigned char voice_ctrl;

	snd_spin_lock(gus, reg, &flags);
	if (gus->gf1.pcm_pflags & SND_GF1_PCM_PFLG_ACTIVE) {
		snd_gf1_select_voice(gus, SND_GF1_PCM_VOICE_MIN());
		voice_ctrl = snd_gf1_i_read8(gus, SND_GF1_VB_ADDRESS_CONTROL);
		gus->gf1.pcm_pos = (snd_gf1_i_read_addr(gus, SND_GF1_VA_CURRENT, voice_ctrl & 4) >> 4) - gus->gf1.pcm_memory;
	}
	snd_spin_unlock(gus, reg, &flags);
	snd_gf1_stop_voices(gus, SND_GF1_PCM_VOICE_MIN(), SND_GF1_PCM_VOICE_MAX());
}

static void snd_gf1_pcm_voices_change_stop(snd_gus_card_t * gus)
{
	snd_pcm1_t *pcm1;

	pcm1 = (snd_pcm1_t *) gus->pcm->private_data;
	snd_gf1_clear_voices(gus, SND_GF1_PCM_VOICE_MIN(), SND_GF1_PCM_VOICE_MAX());
	if ((gus->gf1.pcm_pflags & SND_GF1_PCM_PFLG_ACTIVE) && gus->gf1.pcm_pos != SND_POS_NONE) {
		snd_gf1_pcm_trigger_up(pcm1, gus);
		gus->gf1.pcm_pos = SND_POS_NONE;
	}
}

static void snd_gf1_pcm_volume_change(snd_gus_card_t * gus)
{
}

static int snd_gf1_pcm_playback_open(snd_pcm1_t * pcm1)
{
	int err, pg;
	snd_gus_card_t *gus;
	snd_gf1_mem_block_t *block;
	struct SND_STRU_GF1_VOICE_RANGE *range;

	gus = (snd_gus_card_t *) pcm1->private_data;
	if ((err = snd_pcm1_dma_alloc(pcm1, SND_PCM1_PLAYBACK, gus->gf1.dma1ptr, "GF1 PCM - playback")) < 0)
		return err;
	if ((block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc,
	                               SND_GF1_MEM_OWNER_DRIVER,
				       "GF1 PCM",
	                               pcm1->playback.size, 1, 32,
	                               NULL)) == NULL) {
		snd_pcm1_dma_free(pcm1, SND_PCM1_PLAYBACK, gus->gf1.dma1ptr);
		return -ENOMEM;
	}
	gus->gf1.pcm_buffer = snd_malloc_pages(PAGE_SIZE, &pg, 1);
#if 0
	printk("playback.buffer = 0x%lx, gf1.pcm_buffer = 0x%lx\n", (long) pcm->playback.buffer, (long) gus->gf1.pcm_buffer);
#endif
	if (!gus->gf1.pcm_buffer) {
		snd_gf1_mem_xfree(&gus->gf1.mem_alloc, block);
		snd_pcm1_dma_free(pcm1, SND_PCM1_PLAYBACK, gus->gf1.dma1ptr);
		return -ENOMEM;
	}
	if (snd_gf1_dma_init(gus) < 0) {
		snd_free_pages(gus->gf1.pcm_buffer, PAGE_SIZE);
		snd_gf1_mem_xfree(&gus->gf1.mem_alloc, block);
		snd_pcm1_dma_free(pcm1, SND_PCM1_PLAYBACK, gus->gf1.dma1ptr);
		return -ENOMEM;
	}
	snd_gf1_open(gus, SND_GF1_MODE_PCM_PLAY);
	gus->gf1.pcm_pflags = SND_GF1_PCM_PFLG_NONE;
	range = &gus->gf1.voice_ranges[SND_GF1_VOICE_RANGE_PCM];
	range->interrupt_handler_wave = snd_gf1_pcm_interrupt_wave;
	range->interrupt_handler_volume = snd_gf1_pcm_interrupt_volume;
	range->voices_change_start = snd_gf1_pcm_voices_change_start;
	range->voices_change_stop = snd_gf1_pcm_voices_change_stop;
	range->volume_change = snd_gf1_pcm_volume_change;
	gus->gf1.pcm_memory = block->ptr;
	gus->gf1.pcm_pos = SND_POS_NONE;	/* nothing */
	return 0;
}

static void snd_gf1_pcm_playback_close(snd_pcm1_t * pcm1)
{
	snd_gus_card_t *gus;

	gus = (snd_gus_card_t *) pcm1->private_data;
	snd_gf1_dma_done(gus);
	snd_gf1_set_default_handlers(gus, SND_GF1_HANDLER_RANGE | SND_GF1_VOICE_RANGE_PCM);
	snd_gf1_clear_voices(gus, SND_GF1_PCM_VOICE_MIN(), SND_GF1_PCM_VOICE_MAX());
	gus->gf1.pcm_pflags = SND_GF1_PCM_PFLG_NONE;
	if (SND_GF1_PCM_RVOICES() > 0) {
		SND_GF1_PCM_RVOICES() = 0;
		snd_gf1_reselect_active_voices(gus);
	}
	snd_free_pages(gus->gf1.pcm_buffer, PAGE_SIZE);
	gus->gf1.pcm_buffer = NULL;
	snd_gf1_close(gus, SND_GF1_MODE_PCM_PLAY);
	snd_gf1_mem_free(&gus->gf1.mem_alloc, gus->gf1.pcm_memory);
	snd_pcm1_dma_free(pcm1, SND_PCM1_PLAYBACK, gus->gf1.dma1ptr);
}

static int snd_gf1_pcm_playback_ioctl(snd_pcm1_t * pcm1, unsigned int cmd, unsigned long *arg)
{
	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		pcm1->playback.real_rate = pcm1->playback.rate;
		if (pcm1->playback.real_rate < 5510)
			pcm1->playback.real_rate = 5510;
		if (pcm1->playback.real_rate > SND_GF1_PCM_RATE)
			pcm1->playback.real_rate = SND_GF1_PCM_RATE;
		return 0;
	}
	return -ENXIO;
}

static void snd_gf1_pcm_playback_prepare(snd_pcm1_t * pcm1,
					 unsigned char *buffer,
					 unsigned int size,
					 unsigned int offset,
					 unsigned int count)
{
	snd_gus_card_t *gus;

	gus = (snd_gus_card_t *) pcm1->private_data;
	if (SND_GF1_PCM_RVOICES() != pcm1->playback.voices) {
		SND_GF1_PCM_RVOICES() = pcm1->playback.voices;
		snd_gf1_reselect_active_voices(gus);
	}
	gus->gf1.pcm_bpos = 0;
	gus->gf1.pcm_mmap_mask = 0;
	if (!(pcm1->playback.flags & SND_PCM1_FLG_MMAP)) {
		gus->gf1.pcm_blocks = pcm1->playback.blocks;
		gus->gf1.pcm_block_size = pcm1->playback.block_size;
	} else {
		int i, j, k;

		if (pcm1->playback.real_rate <= 11025)
			j = 128;
		else if (pcm1->playback.real_rate <= 22050)
			j = 256;
		else
			j = 512;
		if (pcm1->playback.mode & SND_PCM1_MODE_16)
			j <<= 1;
		j *= pcm1->playback.voices;
		i = pcm1->playback.block_size;
		k = 1;
		while (j <= i) {
			k <<= 1;
			i >>= 1;
		}
		k >>= 1;
		i <<= 1;
#ifdef CONFIG_SND_DEBUG
		if (i > PAGE_SIZE) {
			snd_printd("ultra: gf1 pcm - MMAP block size overflow\n");
		}
#endif
		gus->gf1.pcm_blocks = (pcm1->playback.used_size / i);
		gus->gf1.pcm_block_size = i;
		gus->gf1.pcm_mmap_mask = (gus->gf1.pcm_blocks / pcm1->playback.blocks) - 1;
	}
#if 0
	printk("pcm blocks = %i, pcm_block_size = 0x%x, mmap_mask = 0x%x, mode = 0x%x\n", gus->gf1.pcm_blocks, gus->gf1.pcm_block_size, gus->gf1.pcm_mmap_mask, pcm->playback.mode);
#endif
	gus->gf1.pcm_bpos = 0;
}

static void snd_gf1_pcm_playback_trigger(snd_pcm1_t * pcm1, int up)
{
	unsigned long flags;
	snd_gus_card_t *gus;

	gus = (snd_gus_card_t *) pcm1->private_data;
	if (up) {
		snd_gf1_pcm_trigger_up(pcm1, gus);
	} else {
#if 0
		int voice;
		for (voice = SND_GF1_PCM_VOICE_MIN(); voice <= SND_GF1_PCM_VOICE_MAX(); voice++) {
			CLI(&flags);
			snd_gf1_select_voice(gus, voice);
			snd_gf1_print_voice_registers(gus);
			STI(&flags);
		}
#endif
		snd_spin_lock(gus, playback, &flags);
		gus->gf1.pcm_pflags &= ~SND_GF1_PCM_PFLG_ACTIVE;
		snd_gf1_stop_voices(gus, SND_GF1_PCM_VOICE_MIN(), SND_GF1_PCM_VOICE_MAX());
		snd_spin_unlock(gus, playback, &flags);
	}
}

static unsigned int snd_gf1_pcm_playback_pointer(snd_pcm1_t * pcm1, unsigned int used_size)
{
	unsigned long flags;
	snd_gus_card_t *gus;
	unsigned int pos;
	unsigned char voice_ctrl;

	gus = (snd_gus_card_t *) pcm1->private_data;
	pos = 0;
	if (pcm1->playback.flags & SND_PCM1_FLG_MMAP) {
		pos = (gus->gf1.pcm_bpos + 1) % gus->gf1.pcm_blocks;
		pos *= gus->gf1.pcm_block_size;
	} else {
		snd_spin_lock(gus, reg, &flags);
		if (gus->gf1.pcm_pflags & SND_GF1_PCM_PFLG_ACTIVE) {
			snd_gf1_select_voice(gus, SND_GF1_PCM_VOICE_MIN());
			voice_ctrl = snd_gf1_i_read8(gus, SND_GF1_VB_ADDRESS_CONTROL);
			pos = (snd_gf1_i_read_addr(gus, SND_GF1_VA_CURRENT, voice_ctrl & 4) >> 4) - gus->gf1.pcm_memory;
		}
		snd_spin_lock(gus, reg, &flags);
	}
#if 0
	return gus->gf1.pcm_bpos * gus->gf1.pcm_block_size;
#else
	return pos;
#endif
}

static void snd_gf1_pcm_playback_dma(snd_pcm1_t * pcm1,
			      unsigned char *buffer, unsigned int offset,
				 unsigned char *user, unsigned int count)
{
	snd_gus_card_t *gus;
	unsigned int off, step, voice;

	gus = (snd_gus_card_t *) pcm1->private_data;
	snd_gf1_pcm_translate(pcm1->playback.voices,
			      user,
			      buffer,
			      offset,
			      count,
			      pcm1->playback.used_size,
			      pcm1->playback.mode,
			      1);
	offset /= pcm1->playback.voices;
	count /= pcm1->playback.voices;
	step = pcm1->playback.used_size / pcm1->playback.voices;
	for (voice = 0; voice < pcm1->playback.voices; voice++) {
		off = offset + (step * voice);
		snd_gf1_pcm_block_change(pcm1, gus, off, count);
	}
}

static void snd_gf1_pcm_playback_dma_move(snd_pcm1_t * pcm1,
					  unsigned char *dbuffer,
					  unsigned int dest_offset,
			 		  unsigned char *sbuffer,
			 		  unsigned int src_offset,
					  unsigned int count)
{
	unsigned int src, dest, step, voice;
	snd_gus_card_t *gus;

	gus = (snd_gus_card_t *) pcm1->private_data;
	dest_offset /= pcm1->playback.voices;
	src_offset /= pcm1->playback.voices;
	count /= pcm1->playback.voices;
	step = pcm1->playback.used_size / pcm1->playback.voices;
	for (voice = 0; voice < pcm1->playback.voices; voice++) {
		src = src_offset + (step * voice);
		dest = dest_offset + (step * voice);
		memcpy(&dbuffer[dest], &sbuffer[src], count);
		snd_gf1_pcm_block_change(pcm1, gus, dest, count);
	}
}

static void snd_gf1_pcm_playback_dma_neutral(snd_pcm1_t * pcm1,
			      		     unsigned char *buffer,
			      		     unsigned int offset,
					     unsigned int count,
					     unsigned char neutral_byte)
{
	unsigned long flags;
	unsigned int off, step, voice;
	snd_gus_card_t *gus;

	gus = (snd_gus_card_t *) pcm1->private_data;
	offset /= pcm1->playback.voices;
	count /= pcm1->playback.voices;
	step = pcm1->playback.used_size / pcm1->playback.voices;
	for (voice = 0; voice < pcm1->playback.voices; voice++) {
		off = offset + (step * voice);
		memset(&buffer[off], neutral_byte, count);
		snd_gf1_pcm_block_change(pcm1, gus, off, count);
	}
	if (pcm1->playback.flags & SND_PCM1_FLG_NEUTRAL) {
#if 0
		printk("waiting!!! - jiffies = %li\n", jiffies);
#endif
		/* ok.. wait a little bit while onboard PCM buffer isn't clear */
		while (gus->gf1.dma_used > 0) {
			snd_spin_lock(gus, neutral, &flags);
			gus->gf1.dma_flags |= SND_GF1_DMA_SLEEP;
			snd_sleep(gus, neutral, 2 * HZ);
			gus->gf1.dma_flags &= ~SND_GF1_DMA_SLEEP;
			snd_spin_unlock(gus, neutral, &flags);
		}
#if 0
		printk("waiting (end)!!! - jiffies = %li\n", jiffies);
#endif
	}
}

static void snd_gf1_pcm_interrupt_dma_read(snd_gus_card_t * gus)
{
	snd_pcm1_t *pcm1;

	pcm1 = (snd_pcm1_t *) gus->pcm->private_data;
	snd_gf1_i_write8(gus, SND_GF1_GB_REC_DMA_CONTROL, 0);	/* disable sampling */
	snd_gf1_i_look8(gus, SND_GF1_GB_REC_DMA_CONTROL);	/* Sampling Control Register */
	pcm1->record.ack(pcm1);
}

static int snd_gf1_pcm_record_open(snd_pcm1_t * pcm1)
{
	int err;
	snd_gus_card_t *gus;

	gus = (snd_gus_card_t *) pcm1->private_data;
	if ((err = snd_pcm1_dma_alloc(pcm1, SND_PCM1_RECORD, gus->gf1.dma2ptr, "GF1 PCM - record")) < 0)
		return err;
	snd_gf1_open(gus, SND_GF1_MODE_PCM_RECORD);
	gus->gf1.interrupt_handler_dma_read = snd_gf1_pcm_interrupt_dma_read;
	return 0;
}

static void snd_gf1_pcm_record_close(snd_pcm1_t * pcm1)
{
	snd_gus_card_t *gus;

	gus = (snd_gus_card_t *) pcm1->private_data;
	snd_gf1_set_default_handlers(gus, SND_GF1_HANDLER_DMA_READ);
	snd_gf1_close(gus, SND_GF1_MODE_PCM_RECORD);
	snd_pcm1_dma_free(pcm1, SND_PCM1_RECORD, gus->gf1.dma2ptr);
}

static int snd_gf1_pcm_record_ioctl(snd_pcm1_t * pcm1,
				    unsigned int cmd, unsigned long *arg)
{
	unsigned int tmp;

	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		pcm1->record.real_rate = pcm1->record.rate;
		if (pcm1->record.real_rate < 5510)
			pcm1->record.real_rate = 5510;
		if (pcm1->record.real_rate > 44100)
			pcm1->record.real_rate = 44100;
		tmp = ((9878400L + (pcm1->record.real_rate << 3)) / (pcm1->record.real_rate << 4)) - 2;
		pcm1->record.real_rate = 9878400L / ((tmp + 2) << 4);
		return 0;
	}
	return -ENXIO;
}

static void snd_gf1_pcm_record_prepare(snd_pcm1_t * pcm1,
				       unsigned char *buffer,
				       unsigned int size,
				       unsigned int offset,
				       unsigned int count)
{
	snd_gus_card_t *gus;
	unsigned short tmp;

#if 0
	printk("record prepare - buffer = 0x%lx, size = 0x%x, offset = 0x%x, count = 0x%x\n", buffer, size, offset, count);
#endif
	gus = (snd_gus_card_t *) pcm1->private_data;
#if 0
	if (gus->gf1.dma2 > 3)
		count >>= 1;
#endif
	tmp = ((9878400L + (pcm1->record.real_rate << 3)) / (pcm1->record.real_rate << 4)) - 2;
	snd_gf1_i_write8(gus, SND_GF1_GB_RECORD_RATE, tmp);
	gus->gf1.pcm_rcntrl_reg = 0x21;		/* IRQ at end, enable & start */
	if (pcm1->record.voices > 1)
		gus->gf1.pcm_rcntrl_reg |= 2;
	if (gus->gf1.dma2ptr->dma > 3)
		gus->gf1.pcm_rcntrl_reg |= 4;
	if (pcm1->record.mode & SND_PCM1_MODE_U)
		gus->gf1.pcm_rcntrl_reg |= 0x80;
	snd_gf1_i_write8(gus, SND_GF1_GB_REC_DMA_CONTROL, 0);	/* disable sampling */
	snd_gf1_i_look8(gus, SND_GF1_GB_REC_DMA_CONTROL);	/* Sampling Control Register */
	snd_dma_program(gus->gf1.dma2ptr->dma, &buffer[offset], count, DMA_MODE_READ);
}

static void snd_gf1_pcm_record_trigger(snd_pcm1_t * pcm1, int up)
{
	unsigned long flags;
	snd_gus_card_t *gus;

	gus = (snd_gus_card_t *) pcm1->private_data;
	snd_spin_lock(gus, reg, &flags);
	snd_gf1_write8(gus, SND_GF1_GB_REC_DMA_CONTROL, up ? gus->gf1.pcm_rcntrl_reg : 0);	/* go!!!! */
	snd_gf1_look8(gus, SND_GF1_GB_REC_DMA_CONTROL);
	snd_spin_unlock(gus, reg, &flags);
}

static unsigned int snd_gf1_pcm_record_pointer(snd_pcm1_t * pcm1, unsigned int used_size)
{
	snd_gus_card_t *gus;

	gus = (snd_gus_card_t *) pcm1->private_data;
	return used_size - snd_dma_residue(gus->gf1.dma2ptr->dma);
}

static void snd_gf1_pcm_free(void *private_data);

static struct snd_stru_pcm1_hardware snd_gf1_pcm_playback =
{
	NULL,			/* private data */
	NULL,			/* private_free */
	SND_PCM1_HW_AUTODMA,	/* flags */
	SND_PCM_FMT_MU_LAW |
	SND_PCM_FMT_S8 | SND_PCM_FMT_U8 |
	SND_PCM_FMT_S16_LE | SND_PCM_FMT_U16_LE,	/* formats */
	SND_PCM_FMT_S8 | SND_PCM_FMT_U8 |
	SND_PCM_FMT_S16_LE | SND_PCM_FMT_U16_LE,	/* hardware formats */
	31,			/* align value */
	6,			/* minimal fragment */
	5510,			/* min. rate */
	SND_GF1_PCM_RATE,	/* max. rate */
	32,			/* max. voices */
	snd_gf1_pcm_playback_open,
	snd_gf1_pcm_playback_close,
	snd_gf1_pcm_playback_ioctl,
	snd_gf1_pcm_playback_prepare,
	snd_gf1_pcm_playback_trigger,
	snd_gf1_pcm_playback_pointer,
	snd_gf1_pcm_playback_dma,
	snd_gf1_pcm_playback_dma_move,
	snd_gf1_pcm_playback_dma_neutral
};

static struct snd_stru_pcm1_hardware snd_gf1_pcm_record =
{
	NULL,			/* private data */
	NULL,			/* private free */
	SND_PCM1_HW_BLOCKPTR,	/* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_S8 | SND_PCM_FMT_U8,	/* formats */
	SND_PCM_FMT_S8 | SND_PCM_FMT_U8,	/* hardware formats */
	0,			/* align value */
	6,			/* minimal fragment */
	5510,			/* min. rate */
	44100,			/* max. rate */
	2,			/* max. voices */
	snd_gf1_pcm_record_open,
	snd_gf1_pcm_record_close,
	snd_gf1_pcm_record_ioctl,
	snd_gf1_pcm_record_prepare,
	snd_gf1_pcm_record_trigger,
	snd_gf1_pcm_record_pointer,
	snd_pcm1_record_dma_ulaw,
	snd_pcm1_dma_move,
	NULL
};

static void snd_gf1_pcm_free(void *private_data)
{
	((snd_gus_card_t *) private_data)->pcm = NULL;
}

static int snd_gf1_pcm_volume_level(int w_flag, int *voices, snd_gus_card_t *gus) 
{
	snd_pcm1_t *pcm1;
	unsigned long flags;
	unsigned short vol;
	unsigned char pan;
	int voice, change = 0;

	pcm1 = (snd_pcm1_t *) gus->pcm->private_data;
	snd_spin_lock(gus, pcm_volume_level, &flags);
	if (!w_flag) {
		voices[0] = gus->gf1.pcm_volume_level_left1;
		voices[1] = gus->gf1.pcm_volume_level_right1;
	} else {
		change = voices[0] != gus->gf1.pcm_volume_level_left1 ||
		         voices[1] != gus->gf1.pcm_volume_level_right1;
		gus->gf1.pcm_volume_level_left1 = voices[0];
		gus->gf1.pcm_volume_level_right1 = voices[1];
		gus->gf1.pcm_volume_level_left = SND_GF1_VOLUME(voices[0]);
		gus->gf1.pcm_volume_level_right = SND_GF1_VOLUME(voices[1]);
	}
	snd_spin_unlock(gus, pcm_volume_level, &flags);
	if (!change)
		return 0;
	/* are we active? */
	if (!(gus->gf1.pcm_pflags & SND_GF1_PCM_PFLG_ACTIVE))
		return 1;
	/* load real volume - better precision */
	for (voice = 0; voice < SND_GF1_PCM_VOICES(); voice++) {
		snd_spin_lock(gus, reg, &flags);
		snd_gf1_select_voice(gus, voice + SND_GF1_PCM_VOICE_MIN());
		snd_gf1_ctrl_stop(gus, SND_GF1_VB_VOLUME_CONTROL);
		if (pcm1->playback.mode & SND_PCM1_MODE_MULTI) {
			vol = snd_gf1_pcm_volume(gus, gus->gf1.pcm_volume[voice], pan = gus->gf1.pcm_pan[voice]);
		} else {
			if (pcm1->playback.voices == 2)
				pan = !voice ? 8 : 128 - 9;
			else
				pan = 64;
			vol = snd_gf1_pcm_volume(gus, 128, pan);
		}
		snd_gf1_write16(gus, SND_GF1_VW_VOLUME, vol << 4);
		snd_spin_unlock(gus, reg, &flags);
	}
	return 1;
}

snd_pcm_t *snd_gf1_pcm_new_device(snd_gus_card_t * gus, snd_kmixer_t * mixer,
				  snd_kmixer_element_t * parent, int pcm_dev)
{
        static struct snd_mixer_element_volume1_range pcm_range[2] = {
		{0, 127, -9600, 0},
		{0, 127, -9600, 0}
	};
	snd_card_t *card;
	snd_pcm_t *pcm;
	snd_pcm1_t *pcm1;
	snd_kmixer_group_t *group;
	snd_kmixer_element_t *nelement1, *nelement2;

	card = gus->card;
	pcm = snd_pcm1_new_device(card, gus->interwave ? "AMD InterWave" : "GF1");
	if (!pcm)
		return NULL;
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	pcm1->private_data = gus;
	pcm1->private_free = snd_gf1_pcm_free;
	memcpy(&pcm1->playback.hw, &snd_gf1_pcm_playback, sizeof(snd_gf1_pcm_playback));
	pcm->info_flags = SND_PCM_INFO_MMAP | SND_PCM_INFO_PLAYBACK;
	if (!gus->interwave && !gus->ess_flag) {
		memcpy(&pcm1->record.hw, &snd_gf1_pcm_record, sizeof(snd_gf1_pcm_record));
		pcm->info_flags |= SND_PCM_INFO_RECORD;
		if (gus->gf1.dma1ptr != gus->gf1.dma2ptr)
			pcm->info_flags |= SND_PCM_INFO_DUPLEX;
	}
	strcpy(pcm->name, pcm->id);
	if (gus->interwave) {
		sprintf(pcm->name + strlen(pcm->name), " rev %c", gus->revision + 'A');
	}
	strcat(pcm->name, " (synth)");
	gus->pcm = pcm;
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_PCM, pcm_dev)) == NULL)
		printk("Oops. Can't register the PCM group for GF1 PCM.");
	if ((nelement1 = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_PLAYBACK, pcm_dev, SND_MIXER_ETYPE_PLAYBACK, 1, &pcm_dev)) == NULL)
		printk("Oops. Can't register the playback mixer element for GF1 PCM.");
	if ((nelement2 = snd_mixer_lib_volume1(mixer, "PCM Volume", pcm_dev, 2, pcm_range, (snd_mixer_volume1_control_t *)snd_gf1_pcm_volume_level, gus)) == NULL)
		printk("Oops. Can't register the PCM Volume mixer element for GF1 PCM.");
	if (group && nelement2)
		snd_mixer_group_element_add(mixer, group, nelement2);
	if (nelement1 && nelement2) {
		snd_mixer_element_route_add(mixer, nelement1, nelement2);
		snd_mixer_element_route_add(mixer, nelement2, parent);
	}
	return pcm;
}
