/*
 *  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 "../../include/driver.h"
#include "../../include/ulaw.h"
#include "../../include/gus.h"
#include "gus_tables.h"

/* maximum rate */

#define SND_GF1_PCM_RATE	48000

#define SND_GF1_PCM_PFLG_NONE		0
#define SND_GF1_PCM_PFLG_ACTIVE		(1<<0)
#define SND_GF1_PCM_PFLG_NEUTRAL	(2<<0)

typedef struct {
	snd_gus_card_t * gus;
	snd_pcm_subchn_t * subchn;
	snd_pcm1_subchn_t * subchn1;
	spinlock_t lock;
	int voices;
	snd_gus_voice_t *pvoices[2];
	unsigned char *buffer;
	unsigned int memory;
	unsigned short flags;
	unsigned char voice_ctrl, ramp_ctrl;
	unsigned int bpos;
	unsigned int blocks;
	unsigned int block_size;
	unsigned int mmap_mask;
	wait_queue_head_t sleep;
	atomic_t dma_count;
	int final_volume;
} pcm_private_t;

#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)
      :	"ebx", "ecx", "edi", "esi", "eax", "memory");
}

#endif

static inline void divide_bytes(unsigned char *dest1,
				unsigned char *dest2,
				unsigned char *src,
				unsigned int count)
{
	__asm__("\tcld\n"
		"\tmov %0, %%ecx\n"
		"\tmov %1, %%esi\n"
		"\tmov %2, %%edi\n"
		"\tmov %3, %%ebx\n"
		"1:\tlodsw\n"
		"\tstosb\n"
		"\txchgb %%ah,%%al\n"
		"\txchgl %%edi,%%ebx\n"
		"\tstosb\n"
		"\txchgl %%edi,%%ebx\n"
		"\tloop 1b\n"
      : /* no output */
      :	"g"(count), "g"((long) src), "g"((long) dest1), "g"((long) dest2)
      :	"bx", "cx", "di", "si", "ax", "memory");
}

static inline void divide_words(unsigned short *dest1,
				unsigned short *dest2,
				unsigned short *src,
				unsigned int count)
{
	__asm__("cld\n"
		"\tmov %0, %%ecx\n"
		"\tmov %1, %%esi\n"
		"\tmov %2, %%edi\n"
		"\tmov %3, %%ebx\n"
		"1:\tlodsw\n"
		"\tstosw\n"
		"\tlodsw\n"
		"\txchgl %%edi,%%ebx\n"
		"\tstosw\n"
		"\txchgl %%edi,%%ebx\n"
		"\tloop 1b\n"
      : /* no output */
      :	"g"(count), "g"((long) src), "g"((long) dest1), "g"((long) 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 inline void divide_bytes(unsigned char *dest1,
				unsigned char *dest2,
				unsigned char *src,
				unsigned int count)
{
	while (count-- > 0) {
		*dest1++ = *src++;
		*dest2++ = *src++;
	}
}

static inline 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 int 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;

	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 -EINVAL;
	}
	if ((pos % tmp) != 0) {
		snd_printd("sound: GF1 PCM problem - unaligned copy (voices = %i, pos = %i)\n", voices, pos);
		return -EINVAL;
	}
	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;
			if (copy_from_user(buffer, src, count1))
				return -EFAULT;
			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) {
					divide_bytes(dest + pos, dest + pos + total, bpos, count1 >> 1);
					pos += 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
	return 0;
}

static void snd_gf1_pcm_block_change_ack(snd_gus_card_t * gus, void *private_data)
{
	pcm_private_t *pcmp = (pcm_private_t *)private_data;

	if (pcmp) {
		atomic_dec(&pcmp->dma_count);
		wake_up(&pcmp->sleep);
	}
}

static void snd_gf1_pcm_block_change_ack_neutral(snd_gus_card_t * gus, void *private_data)
{
	pcm_private_t *pcmp = (pcm_private_t *)private_data;

	if (pcmp) {
		atomic_dec(&pcmp->dma_count);
		pcmp->flags &= ~SND_GF1_PCM_PFLG_NEUTRAL;
		wake_up(&pcmp->sleep);
	}
}

static void snd_gf1_pcm_block_change(snd_pcm1_subchn_t * subchn1,
				     unsigned int offset,
				     unsigned int count,
				     int neutral)
{
	pcm_private_t *pcmp = (pcm_private_t *)subchn1->private_data;
	snd_gf1_dma_block_t block;

#if 0
	snd_printk("block change - offset = 0x%x, count = 0x%x\n", offset, count);
#endif
	if (count < 64) {
		snd_gus_card_t *gus = pcmp->gus;
		unsigned long flags;

		if (gus->interwave) {
			spin_lock_irqsave(&gus->reg_lock, flags);
			snd_gf1_write8(gus, SND_GF1_GB_MEMORY_CONTROL, 0x01);
			snd_gf1_dram_addr(gus, pcmp->memory + offset);
			outsb(GUSP(gus, DRAM), subchn1->buffer + offset, count);
			spin_unlock_irqrestore(&gus->reg_lock, flags);
		} else {
			unsigned int address = pcmp->memory + offset;
			unsigned char *pbuffer = subchn1->buffer + offset;
		
			while (count-- > 0)
				snd_gf1_poke(gus, address++, *pbuffer++);
		}
		return;
	}
	if (offset & 31) {
		count += count & 31;
		offset &= ~31;
	}
	if (count < 32)
		count = 32;
	memset(&block, 0, sizeof(block));
	block.cmd = SND_GF1_DMA_IRQ;
	if (subchn1->mode & SND_PCM1_MODE_U)
		block.cmd |= SND_GF1_DMA_UNSIGNED;
	if (subchn1->mode & SND_PCM1_MODE_16)
		block.cmd |= SND_GF1_DMA_16BIT;
	block.addr = pcmp->memory + offset;
	block.buffer = subchn1->buffer + offset;
	block.count = count;
	block.private_data = pcmp;
	block.ack = neutral ? snd_gf1_pcm_block_change_ack_neutral :
			      snd_gf1_pcm_block_change_ack;
	if (!snd_gf1_dma_transfer_block(pcmp->gus, &block, 0, 0))
		atomic_inc(&pcmp->dma_count);
}

static void snd_gf1_pcm_mmap_change(snd_pcm1_subchn_t * subchn1,
				    unsigned int offset,
				    unsigned int poffset,
				    unsigned int count)
{
	pcm_private_t *pcmp = (pcm_private_t *)subchn1->private_data;
	snd_gf1_dma_block_t block;


#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
	memset(&block, 0, sizeof(block));
	block.cmd = SND_GF1_DMA_IRQ;
	if (subchn1->mode & SND_PCM1_MODE_U)
		block.cmd |= SND_GF1_DMA_UNSIGNED;
	if (subchn1->mode & SND_PCM1_MODE_16)
		block.cmd |= SND_GF1_DMA_16BIT;
	block.addr = pcmp->memory + offset;
	block.buffer = pcmp->buffer + poffset;
	block.count = count;
	block.private_data = pcmp;
	block.ack = snd_gf1_pcm_block_change_ack;
	if (!snd_gf1_dma_transfer_block(pcmp->gus, &block, 1, 0))
		atomic_inc(&pcmp->dma_count);
}

static void snd_gf1_pcm_trigger_up(snd_pcm1_subchn_t * subchn1)
{
	pcm_private_t *pcmp = (pcm_private_t *)subchn1->private_data;
	snd_gus_card_t * gus = pcmp->gus;
	unsigned long flags;
	unsigned char voice_ctrl, ramp_ctrl;
	unsigned short rate;
	unsigned int curr, begin, end;
	unsigned short vol;
	unsigned char pan;
	unsigned int voice;

	if (subchn1 == NULL)
		return;
	spin_lock_irqsave(&pcmp->lock, flags);
	if (pcmp->flags & SND_GF1_PCM_PFLG_ACTIVE) {
		spin_unlock_irqrestore(&pcmp->lock, flags);
		return;
	}
	pcmp->flags |= SND_GF1_PCM_PFLG_ACTIVE;
	pcmp->final_volume = 0;
	spin_unlock_irqrestore(&pcmp->lock, flags);
	rate = snd_gf1_translate_freq(gus, subchn1->real_rate << 4);
	/* enable WAVE IRQ */
	voice_ctrl = subchn1->mode & SND_PCM1_MODE_16 ? 0x24 : 0x20;
	/* enable RAMP IRQ + rollover */
	ramp_ctrl = 0x24;
	for (voice = 0; voice < pcmp->voices; voice++) {
		begin = pcmp->memory + voice * (subchn1->used_size / subchn1->voices);
		curr = begin + (pcmp->bpos * pcmp->block_size) / subchn1->voices;
		end = curr + (pcmp->block_size / subchn1->voices);
		end -= subchn1->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
		pan = subchn1->voices == 2 ? (!voice ? 1 : 14) : 8;
		vol = !voice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right;
		spin_lock_irqsave(&gus->reg_lock, flags);
		snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number);
		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 >> 8);
		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);
		}
		spin_unlock_irqrestore(&gus->reg_lock, flags);
	}
	spin_lock_irqsave(&gus->reg_lock, flags);
	for (voice = 0; voice < pcmp->voices; voice++) {
		snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number);
		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 = 0; voice < pcmp->voices; voice++) {
			snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number);
			snd_gf1_write8(gus, SND_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
			voice_ctrl &= ~0x20;	/* disable IRQ for next voice */
		}
	}
	spin_unlock_irqrestore(&gus->reg_lock, flags);
}

static void snd_gf1_pcm_interrupt_wave(snd_gus_card_t * gus, snd_gus_voice_t *pvoice)
{
	snd_pcm1_subchn_t * subchn1;
	pcm_private_t * pcmp;
	unsigned char voice_ctrl, ramp_ctrl;
	int idx;
	unsigned int end, step;

	if (!pvoice->private_data) {
		snd_printd("ultra_gf1_pcm: unknown wave irq?\n");
		snd_gf1_smart_stop_voice(gus, pvoice->number);
		return;
	}
	pcmp = (pcm_private_t *)pvoice->private_data;
	if (pcmp == NULL) {
		snd_printd("ultra_gf1_pcm: unknown wave irq?\n");
		snd_gf1_smart_stop_voice(gus, pvoice->number);
		return;
	}		
	gus = pcmp->gus;
	subchn1 = pcmp->subchn1;

	spin_lock_irq(&gus->reg_lock);
	snd_gf1_select_voice(gus, pvoice->number);
	voice_ctrl = snd_gf1_read8(gus, SND_GF1_VB_ADDRESS_CONTROL) & ~0x8b;
	ramp_ctrl = (snd_gf1_read8(gus, SND_GF1_VB_VOLUME_CONTROL) & ~0xa4) | 0x03;
	pcmp->bpos++;
	pcmp->bpos %= pcmp->blocks;
	if (pcmp->bpos + 1 >= pcmp->blocks) {	/* last block? */
		voice_ctrl |= 0x08;	/* enable loop */
	} else {
		ramp_ctrl |= 0x04;	/* enable rollover */
	}
	end = pcmp->memory + (((pcmp->bpos + 1) * pcmp->block_size) / subchn1->voices);
	end -= voice_ctrl & 4 ? 2 : 1;
	step = subchn1->used_size / subchn1->voices;
	voice_ctrl |= 0x20;
	if (!pcmp->final_volume) {
		ramp_ctrl |= 0x20;
		ramp_ctrl &= ~0x03;
	}
	for (idx = 0; idx < pcmp->voices; idx++, end += step) {
		snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number);
		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);
		voice_ctrl &= ~0x20;
	}
	if (!gus->gf1.enh_mode) {
		snd_gf1_delay(gus);
		voice_ctrl |= 0x20;
		for (idx = 0; idx < pcmp->voices; idx++) {
			snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number);
			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;
		}
	}
	spin_unlock_irq(&gus->reg_lock);

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

		if ((pcmp->bpos & pcmp->mmap_mask) == 0)
			subchn1->ack(pcmp->subchn);

		mpos = ((pcmp->bpos + 1) % pcmp->blocks) * pcmp->block_size;
#if 1
		snd_gf1_pcm_translate(subchn1->voices,
				      subchn1->buffer + mpos,
				      pcmp->buffer,
				      0,
				      pcmp->block_size,
				      PAGE_SIZE,
				      subchn1->mode,
				      0);
#else
		memset(pcmp->buffer, 0, PAGE_SIZE);
#endif
		mpos /= subchn1->voices;
		count = pcmp->block_size / subchn1->voices;
		step = subchn1->used_size / subchn1->voices;
		pstep = PAGE_SIZE / subchn1->voices;
		for (idx = 0; idx < subchn1->voices; idx++)
			snd_gf1_pcm_mmap_change(subchn1,
						mpos + (idx * step),
						idx * pstep, count);
	} else {
		subchn1->ack(pcmp->subchn);	/* ack to high level */
	}
}

static void snd_gf1_pcm_interrupt_volume(snd_gus_card_t * gus, snd_gus_voice_t * pvoice)
{
	unsigned short vol;
	int cvoice;
	pcm_private_t *pcmp = (pcm_private_t *)pvoice->private_data;
	snd_pcm1_subchn_t *subchn1;

	/* stop ramp, but leave rollover bit untouched */
	spin_lock_irq(&gus->reg_lock);
	snd_gf1_select_voice(gus, pvoice->number);
	snd_gf1_ctrl_stop(gus, SND_GF1_VB_VOLUME_CONTROL);
	spin_unlock_irq(&gus->reg_lock);
	if (pcmp == NULL)
		return;
	/* are we active? */
	if (!(pcmp->flags & SND_GF1_PCM_PFLG_ACTIVE))
		return;
	/* load real volume - better precision */
	cvoice = pcmp->pvoices[0] == pvoice ? 0 : 1;
	subchn1 = pcmp->subchn1;
	if (subchn1 == NULL)
		return;
	vol = !cvoice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right;
	spin_lock_irq(&gus->reg_lock);
	snd_gf1_select_voice(gus, pvoice->number);
	snd_gf1_write16(gus, SND_GF1_VW_VOLUME, vol);
	pcmp->final_volume = 1;
	spin_unlock_irq(&gus->reg_lock);
}

static void snd_gf1_pcm_volume_change(snd_gus_card_t * gus)
{
}

static void snd_gf1_pcm_playback_free(void *private_data)
{
	pcm_private_t * pcmp = (pcm_private_t *)private_data;
	snd_gus_card_t * gus = pcmp->gus;
	int idx;	
	
	for (idx = 0; idx < 2; idx++)
		snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[idx]);
	if (pcmp->buffer != NULL)
		snd_free_pages(pcmp->buffer, PAGE_SIZE);
	if (pcmp->memory > 0)
		snd_gf1_mem_free(&gus->gf1.mem_alloc, pcmp->memory);
	snd_kfree(pcmp);
}

static int snd_gf1_pcm_playback_open(void *private_data,
				     snd_pcm_subchn_t *subchn)
{
	int err, pg;
	pcm_private_t *pcmp;
	snd_gus_card_t *gus = (snd_gus_card_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	snd_gf1_mem_block_t *block;

	pcmp = snd_kcalloc(sizeof(*pcmp), GFP_KERNEL);
	if (pcmp == NULL)
		return -ENOMEM;
	subchn1->private_data = pcmp;
	subchn1->private_free = snd_gf1_pcm_playback_free;
	pcmp->lock = SPIN_LOCK_UNLOCKED;
	init_waitqueue_head(&pcmp->sleep);
	atomic_set(&pcmp->dma_count, 0);
	pcmp->gus = gus;
	if ((pcmp->pvoices[0] = snd_gf1_alloc_voice(gus, SND_GF1_VOICE_TYPE_PCM, 0, 0)) == NULL)
		return -EBUSY;
	if ((pcmp->pvoices[1] = snd_gf1_alloc_voice(gus, SND_GF1_VOICE_TYPE_PCM, 0, 0)) == NULL)
		return -EBUSY;
	pcmp->pvoices[0]->handler_wave =
	pcmp->pvoices[1]->handler_wave = snd_gf1_pcm_interrupt_wave;
	pcmp->pvoices[0]->handler_volume =
	pcmp->pvoices[1]->handler_volume = snd_gf1_pcm_interrupt_volume;
	pcmp->pvoices[0]->volume_change =
	pcmp->pvoices[1]->volume_change = snd_gf1_pcm_volume_change;
	pcmp->pvoices[0]->private_data =
	pcmp->pvoices[1]->private_data = pcmp;
	if ((err = snd_pcm1_dma_alloc(subchn, 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",
	                               subchn1->size, 1, 32,
	                               NULL)) == NULL) {
		snd_pcm1_dma_free(subchn);
		return -ENOMEM;
	}
	pcmp->memory = block->ptr;
	pcmp->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 (!pcmp->buffer) {
		snd_pcm1_dma_free(subchn);
		return -ENOMEM;
	}
	if (snd_gf1_dma_init(gus) < 0) {
		snd_pcm1_dma_free(subchn);
		return -ENOMEM;
	}
	pcmp->flags = SND_GF1_PCM_PFLG_NONE;
	pcmp->subchn = subchn;
	pcmp->subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	return 0;
}

static void snd_gf1_pcm_playback_close(void *private_data,
				       snd_pcm_subchn_t * subchn)
{
	snd_gus_card_t *gus = (snd_gus_card_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pcm_private_t *pcmp = (pcm_private_t *) subchn1->private_data;
	unsigned long jiffies_old;

	jiffies_old = jiffies;
	while (atomic_read(&pcmp->dma_count) > 0) {
		interruptible_sleep_on_timeout(&pcmp->sleep, 1);
		if ((signed long)(jiffies - jiffies_old) > 2*HZ) {
			snd_printk("gf1 pcm - serious DMA problem\n");
			break;
		}
	}
	snd_gf1_dma_done(gus);	
	snd_pcm1_dma_free(subchn);
}

static int snd_gf1_pcm_playback_ioctl(void *private_data,
				      snd_pcm_subchn_t * subchn,
				      unsigned int cmd,
				      unsigned long *arg)
{
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pcm_private_t *pcmp = (pcm_private_t *) subchn1->private_data;

	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		subchn1->real_rate = subchn1->rate;
		if (subchn1->real_rate < 5510)
			subchn1->real_rate = 5510;
		if (subchn1->real_rate > SND_GF1_PCM_RATE)
			subchn1->real_rate = SND_GF1_PCM_RATE;
		return 0;
	case SND_PCM1_IOCTL_VOICES:
		pcmp->voices = subchn1->voices;
		return 0;
	}
	return -ENXIO;
}

static void snd_gf1_pcm_playback_prepare(void *private_data,
					 snd_pcm_subchn_t * subchn,
					 unsigned char *buffer,
					 unsigned int size,
					 unsigned int offset,
					 unsigned int count)
{
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pcm_private_t *pcmp = (pcm_private_t *) subchn1->private_data;

	pcmp->bpos = 0;
	pcmp->mmap_mask = 0;
	if (!(subchn1->flags & SND_PCM1_FLG_MMAP)) {
		pcmp->blocks = subchn1->blocks;
		pcmp->block_size = subchn1->block_size;
	} else {
		int i, j, k;

		if (subchn1->real_rate <= 11025)
			j = 128;
		else if (subchn1->real_rate <= 22050)
			j = 256;
		else
			j = 512;
		if (subchn1->mode & SND_PCM1_MODE_16)
			j <<= 1;
		j *= subchn1->voices;
		i = subchn1->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
		pcmp->blocks = (subchn1->used_size / i);
		pcmp->block_size = i;
		pcmp->mmap_mask = (pcmp->blocks / subchn1->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
	pcmp->bpos = 0;
}

static void snd_gf1_pcm_playback_trigger(void *private_data,
					 snd_pcm_subchn_t * subchn,
					 int up)
{
	unsigned long flags;
	snd_gus_card_t *gus =  (snd_gus_card_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pcm_private_t *pcmp = (pcm_private_t *) subchn1->private_data;
	int voice;

	if (up) {
		snd_gf1_pcm_trigger_up(subchn1);
	} else {
		spin_lock_irqsave(&gus->playback_lock, flags);
		pcmp->flags &= ~SND_GF1_PCM_PFLG_ACTIVE;
		voice = pcmp->pvoices[0]->number;
		snd_gf1_stop_voices(gus, voice, voice);
		voice = pcmp->pvoices[1]->number;
		snd_gf1_stop_voices(gus, voice, voice);
		spin_unlock_irqrestore(&gus->playback_lock, flags);
	}
}

static unsigned int snd_gf1_pcm_playback_pointer(void *private_data,
						 snd_pcm_subchn_t * subchn,
						 unsigned int used_size)
{
	unsigned long flags;
	snd_gus_card_t *gus =  (snd_gus_card_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pcm_private_t *pcmp = (pcm_private_t *) subchn1->private_data;
	unsigned int pos;
	unsigned char voice_ctrl;

	pos = 0;
	if (subchn1->flags & SND_PCM1_FLG_MMAP) {
		pos = (pcmp->bpos + 1) % pcmp->blocks;
		pos *= pcmp->block_size;
	} else {
		spin_lock_irqsave(&gus->reg_lock, flags);
		if (pcmp->flags & SND_GF1_PCM_PFLG_ACTIVE) {
			snd_gf1_select_voice(gus, pcmp->pvoices[0]->number);
			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) - pcmp->memory;
		}
		spin_lock_irqsave(&gus->reg_lock, flags);
	}
	return pos;
}

static int snd_gf1_pcm_playback_dma(void *private_data,
				    snd_pcm_subchn_t * subchn,
				    unsigned char *buffer, unsigned int offset,
				    unsigned char *user, unsigned int count)
{
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pcm_private_t *pcmp = (pcm_private_t *) subchn1->private_data;
	unsigned int off, step, voice, err;

#if 0
	printk("PLAYBACK DMA: buffer = 0x%lx, offset = 0x%x, count = 0x%x\n", (long)buffer, offset, count);
#endif
	err = snd_gf1_pcm_translate(subchn1->voices,
				    user,
				    buffer,
				    offset,
				    count,
				    subchn1->used_size,
				    subchn1->mode,
				    1);
	if (err < 0)
		return err;
	offset /= subchn1->voices;
	count /= subchn1->voices;
	step = subchn1->used_size / subchn1->voices;
	for (voice = 0; voice < subchn1->voices; voice++) {
		off = offset + (step * voice);
		snd_gf1_pcm_block_change(subchn1, off, count, 0);
	}
	if (!in_interrupt()) {
		unsigned long jiffies_old = jiffies;

		while (atomic_read(&pcmp->dma_count) > 0) {
			interruptible_sleep_on_timeout(&pcmp->sleep, 1);
			if ((signed long)(jiffies - jiffies_old) > 2*HZ) {
				snd_printk("gf1 pcm - serious DMA problem\n");
				break;
			}
		}
	}
	return 0;
}

static int snd_gf1_pcm_playback_dma_move(void *private_data,
					 snd_pcm_subchn_t * subchn,
					 unsigned char *dbuffer,
					 unsigned int dest_offset,
			 		 unsigned char *sbuffer,
			 		 unsigned int src_offset,
					 unsigned int count)
{
	unsigned int src, dest, step, voice;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;

	dest_offset /= subchn1->voices;
	src_offset /= subchn1->voices;
	count /= subchn1->voices;
	step = subchn1->used_size / subchn1->voices;
	for (voice = 0; voice < subchn1->voices; voice++) {
		src = src_offset + (step * voice);
		dest = dest_offset + (step * voice);
		memcpy(&dbuffer[dest], &sbuffer[src], count);
		snd_gf1_pcm_block_change(subchn1, dest, count, 0);
	}
	return 0;
}

static int snd_gf1_pcm_playback_dma_neutral(void *private_data,
					    snd_pcm_subchn_t * subchn,
			      		    unsigned char *buffer,
			      		    unsigned int offset,
					    unsigned int count,
					    unsigned char neutral_byte)
{
	unsigned int off, step, voice;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pcm_private_t *pcmp = (pcm_private_t *) subchn1->private_data;

#if 0
	printk("NEUTRAL: buffer = 0x%lx, offset = 0x%x, count = 0x%x\n", (long)buffer, offset, count);
#endif
	offset /= subchn1->voices;
	count /= subchn1->voices;
	step = subchn1->used_size / subchn1->voices;
	pcmp->flags |= SND_GF1_PCM_PFLG_NEUTRAL;
	for (voice = 0; voice < subchn1->voices; voice++) {
		off = offset + (step * voice);
		memset(&buffer[off], neutral_byte, count);
		snd_gf1_pcm_block_change(subchn1, off, count,
					 voice + 1 == subchn1->voices);
	}
	if (!in_interrupt()) {
#if 0
		printk("waiting!!! - jiffies = %li\n", jiffies);
#endif
		/* ok.. wait a little bit while onboard PCM buffer isn't clear */
		while (pcmp->flags & SND_GF1_PCM_PFLG_NEUTRAL) {
			interruptible_sleep_on(&pcmp->sleep);
			if (signal_pending(current)) {
				pcmp->flags &= ~SND_GF1_PCM_PFLG_NEUTRAL;
				break;
			}
		}
#if 0
		printk("waiting (end)!!! - jiffies = %li\n", jiffies);
#endif
	}
	return 0;
}

static void snd_gf1_pcm_interrupt_dma_read(snd_gus_card_t * gus)
{
	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 */
	gus->pcm_cap_subchn1->ack(gus->pcm_cap_subchn);
}

static int snd_gf1_pcm_capture_open(void *private_data,
				    snd_pcm_subchn_t * subchn)
{
	int err;
	snd_gus_card_t *gus = (snd_gus_card_t *) private_data;

	if ((err = snd_pcm1_dma_alloc(subchn, gus->gf1.dma2ptr, "GF1 PCM - record")) < 0)
		return err;
	gus->gf1.interrupt_handler_dma_read = snd_gf1_pcm_interrupt_dma_read;
	gus->pcm_cap_subchn = subchn;
	gus->pcm_cap_subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	return 0;
}

static void snd_gf1_pcm_capture_close(void *private_data,
				      snd_pcm_subchn_t * subchn)
{
	snd_gus_card_t *gus = (snd_gus_card_t *) private_data;

	gus->pcm_cap_subchn = NULL;
	gus->pcm_cap_subchn1 = NULL;
	snd_gf1_set_default_handlers(gus, SND_GF1_HANDLER_DMA_READ);
	snd_pcm1_dma_free(subchn);
}

static int snd_gf1_pcm_capture_ioctl(void *private_data,
				     snd_pcm_subchn_t * subchn,
				     unsigned int cmd,
				     unsigned long *arg)
{
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	unsigned int tmp;

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

static void snd_gf1_pcm_capture_prepare(void *private_data,
				        snd_pcm_subchn_t * subchn,
				        unsigned char *buffer,
				        unsigned int size,
				        unsigned int offset,
				        unsigned int count)
{
	snd_gus_card_t *gus = (snd_gus_card_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	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
#if 0
	if (gus->gf1.dma2 > 3)
		count >>= 1;
#endif
	tmp = ((9878400L + (subchn1->real_rate << 3)) / (subchn1->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 (subchn1->voices > 1)
		gus->gf1.pcm_rcntrl_reg |= 2;
	if (gus->gf1.dma2ptr->dma > 3)
		gus->gf1.pcm_rcntrl_reg |= 4;
	if (subchn1->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_capture_trigger(void *private_data,
				       snd_pcm_subchn_t * subchn,
				       int up)
{
	unsigned long flags;
	snd_gus_card_t *gus = (snd_gus_card_t *) private_data;

	spin_lock_irqsave(&gus->reg_lock, 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);
	spin_unlock_irqrestore(&gus->reg_lock, flags);
}

static unsigned int snd_gf1_pcm_capture_pointer(void *private_data,
					       snd_pcm_subchn_t * subchn,
					       unsigned int used_size)
{
	snd_gus_card_t *gus = (snd_gus_card_t *) 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 =
{
	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 */
	255,			/* align value */
	8,			/* minimal fragment */
	5510,			/* min. rate */
	SND_GF1_PCM_RATE,	/* max. rate */
	2,			/* 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 =
{
	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_capture_open,
	snd_gf1_pcm_capture_close,
	snd_gf1_pcm_capture_ioctl,
	snd_gf1_pcm_capture_prepare,
	snd_gf1_pcm_capture_trigger,
	snd_gf1_pcm_capture_pointer,
	snd_pcm1_capture_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(void *private_data, int w_flag, int *voices) 
{
	snd_gus_card_t *gus = (snd_gus_card_t *)private_data;
	unsigned long flags;
	unsigned short vol;
	int change = 0, idx;
	pcm_private_t *pcmp;
	snd_gus_voice_t *pvoice;

	spin_lock_irqsave(&gus->pcm_volume_level_lock, 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_lvol_to_gvol_raw(voices[0] << 9) << 4;
		gus->gf1.pcm_volume_level_right = snd_gf1_lvol_to_gvol_raw(voices[1] << 9) << 4;
	}
	spin_unlock_irqrestore(&gus->pcm_volume_level_lock, flags);
	if (!change)
		return 0;
	/* are we active? */
	spin_lock_irqsave(&gus->voice_alloc, flags);
	for (idx = 0; idx < 32; idx++) {
		pvoice = &gus->gf1.voices[idx];
		if (!pvoice->pcm)
			continue;
		pcmp = (pcm_private_t *) pvoice->private_data;
		if (!(pcmp->flags & SND_GF1_PCM_PFLG_ACTIVE))
			continue;
		/* load real volume - better precision */
		spin_lock_irqsave(&gus->reg_lock, flags);
		snd_gf1_select_voice(gus, pvoice->number);
		snd_gf1_ctrl_stop(gus, SND_GF1_VB_VOLUME_CONTROL);
		vol = pvoice == pcmp->pvoices[0] ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right;
		snd_gf1_write16(gus, SND_GF1_VW_VOLUME, vol);
		pcmp->final_volume = 1;
		spin_unlock_irqrestore(&gus->reg_lock, flags);
	}
	spin_unlock_irqrestore(&gus->voice_alloc, flags);
	return change;
}

static int snd_gus_mixer_group_pcm(void *private_data,
				   snd_kmixer_file_t * file,
				   int w_flag,
				   snd_mixer_group_t * ugroup)
{
	snd_gus_card_t *codec = (snd_gus_card_t *)private_data;
	int voices[2];
	int change = 0;

	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		snd_gf1_pcm_volume_level(codec, 0, voices);
		ugroup->volume.names.front_left = voices[0];
		ugroup->volume.names.front_right = voices[1];
		ugroup->min = 0;
		ugroup->max = 127;
	} else {
		voices[0] = ugroup->volume.names.front_left & 127;
		voices[1] = ugroup->volume.names.front_right & 127;
		if (snd_gf1_pcm_volume_level(codec, 1, voices) > 0) {
			snd_mixer_element_value_change(file, codec->me_vol_pcm, 0);
			change = 1;
		}
	}
	return change;
}

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_channel_t *pchn1;
	snd_kmixer_group_t *group;

	card = gus->card;
	pcm = snd_pcm1_new_device(card,
				  gus->interwave ? "AMD InterWave" : "GF1",
				  gus->gf1.pcm_voices / 2,
				  !gus->interwave && !gus->ess_flag ? 1 : 0);
	if (!pcm)
		return NULL;
	pcm->private_data = gus;
	pcm->private_free = snd_gf1_pcm_free;
	/* playback setup */
	pchn1 = (snd_pcm1_channel_t *) pcm->playback.private_data;
	memcpy(&pchn1->hw, &snd_gf1_pcm_playback, sizeof(snd_gf1_pcm_playback));
	pchn1->private_data = gus;
	
	pcm->info_flags = SND_PCM_INFO_MMAP | SND_PCM_INFO_PLAYBACK;
	if (!gus->interwave && !gus->ess_flag) {
		/* capture setup */
		pchn1 = (snd_pcm1_channel_t *) pcm->capture.private_data;
		memcpy(&pchn1->hw, &snd_gf1_pcm_record, sizeof(snd_gf1_pcm_record));
		pchn1->private_data = gus;
		pcm->info_flags |= SND_PCM_INFO_CAPTURE;
		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_ctrl(mixer, SND_MIXER_IN_PCM, pcm_dev, gus->ics_flag || gus->card->type == SND_CARD_TYPE_GUS_ACE ? SND_MIXER_OSS_PCM : SND_MIXER_OSS_ALTPCM, snd_gus_mixer_group_pcm, gus)) == NULL)
		printk("Oops. Can't register the PCM group for GF1 PCM.");
	if ((gus->me_playback = 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 ((gus->me_vol_pcm = snd_mixer_lib_volume1(mixer, "PCM Volume", pcm_dev, 2, pcm_range, snd_gf1_pcm_volume_level, gus)) == NULL)
		printk("Oops. Can't register the PCM Volume mixer element for GF1 PCM.");
	if (group && gus->me_vol_pcm)
		snd_mixer_group_element_add(mixer, group, gus->me_vol_pcm);
	if (gus->me_playback && gus->me_vol_pcm) {
		snd_mixer_element_route_add(mixer, gus->me_playback, gus->me_vol_pcm);
		snd_mixer_element_route_add(mixer, gus->me_vol_pcm, parent);
	}
	return pcm;
}

