/*
 *  Digital Audio (PCM) abstract layer
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *
 *
 *   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_MAIN_OBJECT_FILE
#include "../../include/driver.h"
#include "../../include/minors.h"
#include "../../include/control.h"
#include "../../include/info.h"
#include "../../include/pcm1.h"
#include "../../include/ulaw.h"

#if 0
#define SND_PCM1_DEBUG_BUFFERS
#endif

static unsigned int snd_pcm1_align1(snd_pcm1_subchn_t * subchn1, unsigned int value);
static int snd_pcm1_playback_pause(snd_pcm_subchn_t * subchn, int enable);

/*

 */

int snd_pcm1_dma_alloc(snd_pcm_subchn_t * subchn, snd_dma_t * dma, char *ident)
{
	int err;
	snd_pcm1_subchn_t *subchn1;
	snd_dma_area_t *area;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	if (subchn1 == NULL)
		return -EINVAL;
	if ((err = snd_dma_malloc(subchn->pcm->card, dma, ident, &area)) < 0)
		return err;
	subchn1->size = area->size;
	subchn1->buffer = area->buf;
	subchn1->dmaptr = area;
	subchn1->frag_size = 0;
	if (subchn1->size < 4096) {
		snd_printd("Invalid audio DMA size - %i\n", subchn1->size);
		snd_dma_free(subchn->pcm->card, area);
		return -ENOMEM;
	}
	subchn1->flags |= SND_PCM1_FLG_DMAOK;
	return 0;
}

int snd_pcm1_dma_free(snd_pcm_subchn_t * subchn)
{
	snd_pcm1_subchn_t *subchn1;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	if (!(subchn1->flags & SND_PCM1_FLG_DMAOK))
		return -EINVAL;
	snd_dma_free(subchn->pcm->card, subchn1->dmaptr);
	subchn1->dmaptr = NULL;
	subchn1->flags &= ~SND_PCM1_FLG_DMAOK;
	return 0;
}

void snd_pcm1_clear_subchannel(snd_pcm_subchn_t * subchn)
{
	snd_pcm1_subchn_t *subchn1;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	subchn1->voices = 1;
	subchn1->mode = 0;
	subchn1->format = 0;
	subchn1->rate = 0;
	subchn1->real_rate = 0;
	subchn1->requested_block_size = 0;
	subchn1->requested_blocks = 0;
	subchn1->requested_subdivision = 0;
	subchn1->processed_bytes = 0;
	subchn1->interrupts = 0;
	subchn1->xruns = 0;
	subchn1->lastxruns = 0;
	subchn1->overrange = 0;
	subchn1->flags &= SND_PCM1_FLG_TIMER;
	subchn1->used_size = 0;
	subchn1->mmap_size = 0;
	subchn1->neutral_byte = 0;
	subchn1->size = 0;
	subchn1->buffer = NULL;
	subchn1->dmaptr = NULL;
	subchn1->blocks = 0;
	subchn1->block_size = 0;
	subchn1->used = 0;
	subchn1->frag_size = 0;
	subchn1->head = 0;
	subchn1->tail = 0;
	subchn1->blocks_max = 0;
	subchn1->blocks_room = 0;
	subchn1->blocks_min = 0;
	snd_pcm1_clear_time(subchn1);
	subchn->timer_resolution = 100;
}

void snd_pcm1_fill_with_neutral(snd_pcm_subchn_t * subchn)
{
	unsigned int size;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	size = subchn1->used_size;
	/* a lowlevel driver does size conversion */
	if (pchn1->hw.dma_neutral) {
		if (size > 0)
			pchn1->hw.dma_neutral(pchn1->private_data, subchn,
					      subchn1->buffer, 0, size,
					      subchn1->neutral_byte);
		subchn1->flags &= ~SND_PCM1_FLG_NEUTRAL;
		return;
	}
	if (pchn1->hw.flags & SND_PCM1_HW_8BITONLY) {
		if (subchn1->mode & SND_PCM1_MODE_16)
			size >>= 1;
	}
	if (pchn1->hw.flags & SND_PCM1_HW_16BITONLY) {
		if (!(subchn1->mode & SND_PCM1_MODE_16))
			size <<= 1;
	}
#ifdef CONFIG_SND_DEBUG
	if (size > subchn1->size) {
		snd_printk("pcm: Oops, neutral fill size %i > DMA size %i\n", size, subchn1->size);
		size = subchn1->size;
	}
#endif
	if (size > 0)
		memset(subchn1->buffer, subchn1->neutral_byte, size);
	subchn1->flags &= ~SND_PCM1_FLG_NEUTRAL;
}

static unsigned int snd_pcm1_bytes_per_second(snd_pcm1_subchn_t * subchn1)
{
	unsigned int bps;

	bps = subchn1->real_rate * subchn1->voices;
	if (subchn1->mode & SND_PCM1_MODE_16) {
		bps <<= 1;
	} else if (subchn1->mode & SND_PCM1_MODE_24) {
		bps *= 3;
	} else if (subchn1->mode & (SND_PCM1_MODE_32|SND_PCM1_MODE_FLOAT)) {
		bps <<= 2;
	} else if (subchn1->mode & SND_PCM1_MODE_FLOAT64) {
		bps <<= 3;
	} else if (subchn1->mode & SND_PCM1_MODE_ADPCM) {
		bps >>= 2;
	}
	if (bps == 0) {
#if 0
		snd_printd("Aiee, bps == 0, real_rate = %i, voices = %i\n", pchn->real_rate, pchn->voices);
#endif
		return 0;
	}
	return bps;
}

static void snd_pcm1_new_timer_resolution(snd_pcm_subchn_t *subchn)
{
	unsigned int bps, sec, bsize;
	snd_pcm1_subchn_t *subchn1;
	
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	/* this is not very good algorithm */
	sec = 1000000000;
	bps = snd_pcm1_bytes_per_second(subchn1);
	bsize = subchn1->block_size;
	if (!bps) {
		subchn->timer_resolution = 100;
		return;
	}
	while (!(bps % 5) && !(sec % 5)) {
		bps /= 5;
		sec /= 5;
	}
	while (!(bps & 1) && !(sec & 1)) {
		bps >>= 1;
		sec >>= 1;
	}
	while (!(bps % 5) && !(bsize % 5)) {
		bps /= 5;
		bsize /= 5;
	}
	while (!(bps & 1) && !(bsize & 1)) {
		bps >>= 1;
		bsize >>= 1;
	}
#if 0
	printk("sec = %u, bps = %u, bsize = %u\n", sec, bps, bsize);
#endif
	while ((sec * bsize) / bsize != sec) {
		if (bsize > sec) {
			bsize >>= 1;
		} else {
			sec >>= 1;
		}
		bps >>= 1;
	}
	subchn->timer_resolution = (sec * bsize) / bps;
#if 0
	printk("sec = %u, bps = %u, block_size = %u, resolution = %u\n", sec, bps, bsize, pchn->timer_resolution);
#endif
}

/*
 *  some shared routines for lowlevel driver
 */

int snd_pcm1_playback_dma(void *private_data,
			  snd_pcm_subchn_t * subchn,
			  unsigned char *buffer, unsigned int offset,
			  unsigned char *user, unsigned int count)
{
	if (copy_from_user(&buffer[offset], user, count))
		return -EFAULT;
	return 0;
}

int snd_pcm1_playback_dma_ulaw(void *private_data,
				snd_pcm_subchn_t * subchn,
			        unsigned char *buffer, unsigned int offset,
				unsigned char *user, unsigned int count)
{
	if (((snd_pcm1_subchn_t *)(subchn->private_data))->mode & SND_PCM1_MODE_ULAW) {
		if (snd_translate_from_user(snd_ulaw_dsp, &buffer[offset],
					    user, count))
			return -EFAULT;
	} else {
		if (copy_from_user(&buffer[offset], user, count))
			return -EFAULT;
	}
	return 0;
}

int snd_pcm1_playback_dma_ulaw_loud(void *private_data,
				    snd_pcm_subchn_t * subchn,
			      	    unsigned char *buffer, unsigned int offset,
				    unsigned char *user, unsigned int count)
{
	if (((snd_pcm1_subchn_t *)(subchn->private_data))->mode & SND_PCM1_MODE_ULAW) {
		if (snd_translate_from_user(snd_ulaw_dsp_loud, &buffer[offset],
				            user, count))
			return -EFAULT;
	} else {
		if (copy_from_user(&buffer[offset], user, count))
			return -EFAULT;
	}
	return 0;
}

int snd_pcm1_playback_dma_neutral(void *private_data,
				  snd_pcm_subchn_t * subchn,
			          unsigned char *buffer, unsigned int offset,
				  unsigned int count,
				  unsigned char neutral_byte)
{
	memset(&buffer[offset], neutral_byte, count);
	return 0;
}

int snd_pcm1_capture_dma(void *private_data,
			 snd_pcm_subchn_t * subchn,
			 unsigned char *buffer, unsigned int offset,
			 unsigned char *user, unsigned int count)
{
	if (copy_to_user(user, &buffer[offset], count))
		return -EFAULT;
	return 0;
}

int snd_pcm1_capture_dma_ulaw(void *private_data,
			      snd_pcm_subchn_t * subchn,
			      unsigned char *buffer, unsigned int offset,
			      unsigned char *user, unsigned int count)
{
	if (((snd_pcm1_subchn_t *)(subchn->private_data))->mode & SND_PCM1_MODE_ULAW) {
		if (snd_translate_to_user(snd_dsp_ulaw, user,
				          &buffer[offset], count))
			return -EFAULT;
	} else {
		if (copy_to_user(user, &buffer[offset], count))
			return -EFAULT;
	}
	return 0;
}

int snd_pcm1_capture_dma_ulaw_loud(void *private_data,
				   snd_pcm_subchn_t * subchn,
			      	   unsigned char *buffer, unsigned int offset,
				   unsigned char *user, unsigned int count)
{
	if (((snd_pcm1_subchn_t *)(subchn->private_data))->mode & SND_PCM1_MODE_ULAW) {
		if (snd_translate_to_user(snd_dsp_ulaw_loud, user,
				          &buffer[offset], count))
			return -EFAULT;
	} else {
		if (copy_to_user(user, &buffer[offset], count))
			return -EFAULT;
	}
	return 0;
}

int snd_pcm1_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)
{
	memcpy(&dbuffer[dest_offset], &sbuffer[src_offset], count);
	return 0;
}

/*
 *  interrupt callbacks from lowlevel driver
 */

static inline int snd_pcm1_playback_ok(snd_pcm1_subchn_t * subchn1)
{
	unsigned long flags;
	int result, freeblks;

	spin_lock_irqsave(&subchn1->lock, flags);
	freeblks = subchn1->blocks - subchn1->used;
	result = subchn1->used <= subchn1->blocks_max &&
	         freeblks >= subchn1->blocks_room;
	spin_unlock_irqrestore(&subchn1->lock, flags);
	return result;
}

static inline int snd_pcm1_capture_ok(snd_pcm1_subchn_t * subchn1)
{
	unsigned long flags;
	int result;

	spin_lock_irqsave(&subchn1->lock, flags);
	if ((subchn1->flags & SND_PCM1_FLG_NEEDEMPTY) && subchn1->used > 0) {
		result = 1;
	} else {
		result = subchn1->used >= subchn1->blocks_min;
	}
	spin_unlock_irqrestore(&subchn1->lock, flags);
	return result;
}

static void snd_pcm1_interrupt_playback(snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	int trigger = 0;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	if (subchn1 == NULL || subchn->pchn == NULL)
		return;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	if (pchn1 == NULL)
		return;
	if (!(subchn1->flags & SND_PCM1_FLG_TRIGGER)) {
		snd_printd("snd_pcm1_interrupt_playback: Oops, playback interrupt when playback isn't active!!!\n");
		return;
	}
	if (subchn1->used <= 0) {
		snd_printd("snd_pcm1_interrupt_playback: Internal error (1)\n");
		return;
	}
	if (subchn1->flags & SND_PCM1_FLG_PAUSE) {
		pchn1->hw.trigger(pchn1->private_data, subchn, 0);
		return;
	}
	if (pchn1->hw.flags & SND_PCM1_HW_AUTODMA) {
		/* try clear first few bytes from non-active block */
		pchn1->hw.dma_neutral(pchn1->private_data,
				      subchn, subchn1->buffer,
				      subchn1->tail * subchn1->block_size,
				      16, subchn1->neutral_byte);
	}
	spin_lock_irqsave(&subchn1->lock, flags);
	if (subchn1->used == 1) {
		if (!(subchn1->flags & SND_PCM1_FLG_SYNC)) {
			subchn1->total_xruns++;
			subchn1->xruns++;
		}
	}
	subchn1->interrupts++;
	if (subchn1->used > 0) {
		subchn1->used--;
		subchn1->processed_bytes += subchn1->block_size;
	}
	subchn1->tail++;
	subchn1->tail %= subchn1->blocks;
	if (subchn1->used > 0) {
		trigger = 1;
	}
	spin_unlock_irqrestore(&subchn1->lock, flags);
	if (trigger) {
		if (!(pchn1->hw.flags & SND_PCM1_HW_AUTODMA)) {
			pchn1->hw.prepare(pchn1->private_data,
					  subchn,
					  subchn1->buffer, subchn1->used_size,
					  subchn1->tail * subchn1->block_size,
					  subchn1->block_size);
			pchn1->hw.trigger(pchn1->private_data, subchn, 1);
		}
	} else {
		if (pchn1->hw.flags & SND_PCM1_HW_AUTODMA)
			pchn1->hw.trigger(pchn1->private_data, subchn, 0);
		snd_pcm1_clear_time(subchn1);
		subchn1->flags &= ~SND_PCM1_FLG_TRIGGER;
	}
	if (snd_pcm1_playback_ok(subchn1))
		wake_up(&subchn1->sleep);
	if (subchn->timer && subchn->timer_running)
		snd_timer_interrupt(subchn->timer, 1);
}

static void snd_pcm1_interrupt_capture(snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	int trigger;
	
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	if (subchn1 == NULL || subchn->pchn == NULL)
		return;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	if (pchn1 == NULL)
		return;
	if (!(subchn1->flags & SND_PCM1_FLG_TRIGGER)) {
		snd_printd("snd_pcm1_interrupt_capture: Oops, playback interrupt when playback isn't active!!!\n");
		return;
	}
	trigger = 0;
	spin_lock_irqsave(&subchn1->lock, flags);
	if (!(subchn1->flags & SND_PCM1_FLG_SYNC))
		trigger = 1;
	if (subchn1->used < subchn1->blocks) {
		subchn1->used++;
	} else {
		subchn1->total_xruns++;
		subchn1->xruns++;
		trigger = 0;
	}
	subchn1->interrupts++;
	subchn1->processed_bytes += subchn1->block_size;
	subchn1->head++;
	subchn1->head %= subchn1->blocks;
	spin_unlock_irqrestore(&subchn1->lock, flags);
	if (trigger) {
		if (!(pchn1->hw.flags & SND_PCM1_HW_AUTODMA)) {
			pchn1->hw.prepare(pchn1->private_data,
					  subchn,
					  subchn1->buffer, subchn1->used_size,
					  subchn1->head * subchn1->block_size,
					  subchn1->block_size);
			pchn1->hw.trigger(pchn1->private_data, subchn, 1);
		}
	} else {
		if (pchn1->hw.flags & SND_PCM1_HW_AUTODMA)
			pchn1->hw.trigger(pchn1->private_data, subchn, 0);
		snd_pcm1_clear_time(subchn1);
		subchn1->flags &= ~SND_PCM1_FLG_TRIGGER;
	}
	if (snd_pcm1_capture_ok(subchn1))
		wake_up(&subchn1->sleep);
	if (subchn->timer && subchn->timer_running)
		snd_timer_interrupt(subchn->timer, 1);
}

/*
 *  trigger standard buffered playback
 */

static void snd_pcm1_trigger_playback(snd_pcm_subchn_t * subchn)
{
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	unsigned long flags;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	if (!subchn1->used)
		return;
	spin_lock_irqsave(&subchn1->lock, flags);
	if (!(subchn1->flags & SND_PCM1_FLG_TRIGGER) &&
	    !(subchn1->flags & SND_PCM1_FLG_PAUSE)) {
		subchn1->flags |= SND_PCM1_FLG_TRIGGERA;
		spin_unlock_irqrestore(&subchn1->lock, flags);
		pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
		subchn1->lastxruns = subchn1->xruns;
		if (subchn1->tail != 0)
			snd_printd( "Oops, playback start when tail = %i\n", subchn1->tail);
		pchn1->hw.prepare(pchn1->private_data,
				  subchn,
				  subchn1->buffer, subchn1->used_size,
				  subchn1->tail * subchn1->block_size,
				  subchn1->block_size);
		pchn1->hw.trigger(pchn1->private_data, subchn, 1);
		if (subchn1->flags & SND_PCM1_FLG_TIME) {
			do_gettimeofday(&subchn1->time);
		}
	} else {
		spin_unlock_irqrestore(&subchn1->lock, flags);
	}
}

/* must be called when playback spinlock is active!!! */

static void snd_pcm1_playback_underrun(snd_pcm_subchn_t *subchn)
{
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	if (subchn1->frag_size > 0)
		pchn1->hw.dma_move(pchn1,
				   subchn,
				   subchn1->buffer, 0,
				   subchn1->buffer,
			           subchn1->head * subchn1->block_size,
			           subchn1->frag_size);
	subchn1->head = subchn1->tail = 0;
	subchn1->lastxruns = subchn1->xruns;
}

/*
 *  user to dma
 */

static int snd_pcm1_user_to_buffer(struct file *file,
				   snd_pcm_subchn_t * subchn,
				   const char *buf, int count)
{
	unsigned long flags;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	int result, tmp, err;
	long timeout;

	if (count <= 0 || !buf)
		return 0;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;

	count = snd_pcm1_align1(subchn1, count);
	if (count <= 0)
		return 0;
		
	if (subchn1->flags & SND_PCM1_FLG_NEUTRAL)
		snd_pcm1_fill_with_neutral(subchn);

	result = 0;

	/*
	 * OK. This is patch for .au files which begin with .snd header...
	 * This is a little bit hard - it's an application work to do conversions...
	 */
	if ((subchn1->mode & SND_PCM1_MODE_ULAW) &&
	    !subchn1->processed_bytes && !subchn1->used && count > 31) {
		unsigned char buffer[8];

		if (copy_from_user(buffer, buf, 8))
			return -EFAULT;
		if (!memcmp(buffer, ".snd", 4)) {
			tmp = (buffer[4] << 24) | (buffer[5] << 16) |
			      (buffer[6] << 8) | (buffer[7]);
			if (tmp > count)
				tmp = count;
			if (tmp < 128) {
				buf += tmp;
				count -= tmp;
				result += tmp;
			}
		}
	}

	while (count > 0) {
		while (!snd_pcm1_playback_ok(subchn1)) {
			if (!(subchn1->flags & SND_PCM1_FLG_TRIGGER))
				snd_pcm1_trigger_playback(subchn);
			if (file->f_flags & O_NONBLOCK)
				return result;
			if (subchn1->flags & SND_PCM1_FLG_PAUSE)
				return result;
			if (!(subchn1->flags & SND_PCM1_FLG_ENABLE))
				return result;
			timeout = interruptible_sleep_on_timeout(&subchn1->sleep, 10 * HZ);
			if (signal_pending(current))
				return !result ? -EINTR : result;
			if (subchn1->used >= subchn1->blocks && !timeout) {
				snd_printd("pcm_user_to_dma: timeout, new block discarded\n");
				return -EIO;
			}
		}

		/* ok.. now we have right block - fill it */

		spin_lock_irqsave(&subchn1->lock, flags);
		tmp = subchn1->block_size - subchn1->frag_size;
		if (tmp > count)
			tmp = count;	/* correction */
		if (subchn1->lastxruns != subchn1->xruns)
			snd_pcm1_playback_underrun(subchn);
		spin_unlock_irqrestore(&subchn1->lock, flags);

		err = pchn1->hw.dma(pchn1->private_data,
				    subchn,
				    subchn1->buffer,
				    (subchn1->head * subchn1->block_size) + subchn1->frag_size,
				    (char *) buf, tmp);
		if (err < 0)
			return err;
		snd_pcm_proc_write(subchn, buf, tmp);

		count -= tmp;
		result += tmp;
		buf += tmp;

		spin_lock_irqsave(&subchn1->lock, flags);
		subchn1->frag_size += tmp;
		if (subchn1->lastxruns != subchn1->xruns)	/* oops, underrun */
			snd_pcm1_playback_underrun(subchn);
		if (subchn1->frag_size >= subchn1->block_size) {
			subchn1->used++;
			subchn1->head++;
			subchn1->head %= subchn1->blocks;
			subchn1->frag_size = 0;
		}
		spin_unlock_irqrestore(&subchn1->lock, flags);
	}
	if (subchn1->used > 0 && !(subchn1->flags & SND_PCM1_FLG_PAUSE))
		snd_pcm1_trigger_playback(subchn);
	return result;
}

/*
 *  trigger standard buffered capture
 */

static void snd_pcm1_trigger_capture(snd_pcm_subchn_t * subchn)
{
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	unsigned long flags;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	if (subchn1->used >= subchn1->blocks)
		return;
	spin_lock_irqsave(&subchn1->lock, flags);
	subchn1->flags &= ~SND_PCM1_FLG_NEEDEMPTY;
	if (!(subchn1->flags & SND_PCM1_FLG_TRIGGER)) {
		subchn1->flags |= SND_PCM1_FLG_TRIGGERA;
		spin_unlock_irqrestore(&subchn1->lock, flags);
		pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
		subchn1->lastxruns = subchn1->xruns;
		if (!subchn1->used)
			subchn1->head = subchn1->tail = 0;
		if (subchn1->head != 0)
			snd_printd( "Oops, capture start when head = %i\n", subchn1->head);
		pchn1->hw.prepare(pchn1->private_data,
				  subchn,
				  subchn1->buffer, subchn1->used_size,
				  subchn1->head * subchn1->block_size,
				  subchn1->block_size);
		pchn1->hw.trigger(pchn1->private_data, subchn, 1);
		if (subchn1->flags & SND_PCM1_FLG_TIME) {
			do_gettimeofday(&subchn1->time);
		}
	} else {
		spin_unlock_irqrestore(&subchn1->lock, flags);
	}
}

/*
 *  dma to user
 */

static int snd_pcm1_buffer_to_user(struct file *file,
				   snd_pcm_subchn_t * subchn,
				   char *buf, int count)
{
	unsigned long flags;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	int result, tmp, err;
	long timeout;

	if (count <= 0)
		return 0;
	if (verify_area(VERIFY_WRITE, buf, count))
		return -EFAULT;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;

	count = snd_pcm1_align1(subchn1, count);
	if (count <= 0)
		return 0;

	if (subchn1->flags & SND_PCM1_FLG_NEUTRAL)
		snd_pcm1_fill_with_neutral(subchn);

	if (!(subchn1->flags & SND_PCM1_FLG_ENABLE))
		return 0;
	result = 0;

	while (count > 0) {
		while (!snd_pcm1_capture_ok(subchn1)) {
			snd_pcm1_trigger_capture(subchn);
			if (file->f_flags & O_NONBLOCK)
				return result;
			timeout = interruptible_sleep_on_timeout(&subchn1->sleep, 10 * HZ);
			if (signal_pending(current))
				return !result ? -EINTR : result;
			if (!subchn1->used && !timeout) {
				snd_printd("snd_pcm_dma_to_user: data timeout\n");
				return -EIO;
			}
		}

		spin_lock_irqsave(&subchn1->lock, flags);
		tmp = count <= subchn1->frag_size ? count : subchn1->frag_size;
		spin_unlock_irqrestore(&subchn1->lock, flags);
		err = pchn1->hw.dma(pchn1->private_data,
				    subchn,
				    subchn1->buffer,
				    (subchn1->tail * subchn1->block_size) +
					(subchn1->block_size - subchn1->frag_size),
				    buf, tmp);
		if (err < 0)
			return err;
		snd_pcm_proc_write(subchn, buf, tmp);

		buf += tmp;
		count -= tmp;
		result += tmp;

		spin_lock_irqsave(&subchn1->lock, flags);
		subchn1->frag_size -= tmp;
		if (subchn1->lastxruns != subchn1->xruns) {	/* oops, overrun */
			subchn1->flags |= SND_PCM1_FLG_NEEDEMPTY;
			subchn1->lastxruns = subchn1->xruns;
		}
		if (!subchn1->frag_size) {
			subchn1->used--;
			subchn1->tail++;
			subchn1->tail %= subchn1->blocks;
			subchn1->frag_size = subchn1->block_size;
		}
		if ((subchn1->flags & SND_PCM1_FLG_NEEDEMPTY) && !subchn1->used) {
			subchn1->head = subchn1->tail = 0;
		}
		spin_unlock_irqrestore(&subchn1->lock, flags);
	}

	return result;
}

/*
 *  synchronize playback
 */

static int snd_pcm1_drain_playback(snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;

#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("drain playback!!!\n");
#endif
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	spin_lock_irqsave(&subchn1->lock, flags);
	if ((subchn1->flags & SND_PCM1_FLG_TRIGGER) ||
	    (subchn1->flags & SND_PCM1_FLG_PAUSE)) {
		pchn1->hw.trigger(pchn1->private_data, subchn, 0);
		snd_pcm1_clear_time(subchn1);
		subchn1->flags &= ~SND_PCM1_FLG_TRIGGER;
	}
	spin_unlock_irqrestore(&subchn1->lock, flags);
	subchn1->flags |= SND_PCM1_FLG_NEUTRAL;
	snd_pcm1_fill_with_neutral(subchn);
	spin_lock_irqsave(&subchn1->lock, flags);
	subchn1->flags &= ~SND_PCM1_FLG_TRIGGER1;
	subchn1->head = subchn1->tail = subchn1->used = 0;
	subchn1->frag_size = subchn1->processed_bytes = 0;
	spin_unlock_irqrestore(&subchn1->lock, flags);
	return 0;
}

static int snd_pcm1_flush_playback(snd_pcm_subchn_t * subchn, int close_flag)
{
	int err = 0;
	unsigned long flags;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	long timeout;

#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("flush playback!!!\n");
#endif
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	if (subchn1->flags & SND_PCM1_FLG_PAUSE)
		snd_pcm1_playback_pause(subchn, 0);
	spin_lock_irqsave(&subchn1->lock, flags);
	subchn1->flags |= SND_PCM1_FLG_SYNC;
	if (subchn1->used < subchn1->blocks && subchn1->frag_size > 0) {

		unsigned int size, count, offset;

		if (subchn1->lastxruns != subchn1->xruns) /* oops, underrun */
			snd_pcm1_playback_underrun(subchn);
		size = subchn1->frag_size;
		count = subchn1->block_size - size;
		offset = (subchn1->head * subchn1->block_size) + size;
		subchn1->used++;
		subchn1->head++;
		subchn1->head %= subchn1->blocks;
		spin_unlock_irqrestore(&subchn1->lock, flags);
		if (count > 0)
			pchn1->hw.dma_neutral(pchn1->private_data,
					      subchn, subchn1->buffer,
					      offset, count,
					      subchn1->neutral_byte);
	} else {
		spin_unlock_irqrestore(&subchn1->lock, flags);
	}
	if (!subchn1->used) {
		subchn1->flags &= ~SND_PCM1_FLG_SYNC;
		snd_pcm1_drain_playback(subchn);
		return 0;
	}
	snd_pcm1_trigger_playback(subchn);

	while (subchn1->used) {
		timeout = interruptible_sleep_on_timeout(&subchn1->sleep, 10 * HZ);
		if (signal_pending(current)) {
			err = -EINTR;
			break;
		}
		if (subchn1->used && !timeout) {
			snd_printd("snd_pcm1_flush_playback: timeout, skipping waiting blocks\n");
			err = -EAGAIN;
			break;
		}
	}
	subchn1->flags &= ~SND_PCM1_FLG_SYNC;
	if (close_flag || (err != -EINTR && err != -EAGAIN))
		snd_pcm1_drain_playback(subchn);
	return err;
}

static int snd_pcm1_drain_record(snd_pcm_subchn_t *subchn)
{
	unsigned long flags;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;

#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("drain record!!!\n");
#endif
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	spin_lock_irqsave(&subchn1->lock, flags);
	if (subchn1->flags & SND_PCM1_FLG_TRIGGER) {
		pchn1->hw.trigger(pchn1->private_data, subchn, 0);
		snd_pcm1_clear_time(subchn1);
		subchn1->flags &= ~SND_PCM1_FLG_TRIGGER;
	}
	spin_unlock_irqrestore(&subchn1->lock, flags);
	subchn1->flags |= SND_PCM1_FLG_NEUTRAL;
	snd_pcm1_fill_with_neutral(subchn);
	spin_lock_irqsave(&subchn1->lock, flags);
	subchn1->flags &= ~SND_PCM1_FLG_TRIGGER1;
	subchn1->head = subchn1->tail = subchn1->used = 0;
	subchn1->frag_size = subchn1->block_size;
	subchn1->processed_bytes = 0;
	spin_unlock_irqrestore(&subchn1->lock, flags);
	return 0;
}

static int snd_pcm1_flush_record(snd_pcm_subchn_t * subchn)
{
	int err = 0;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	long timeout;

#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("flush record!!!\n");
#endif
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	subchn1->flags |= SND_PCM1_FLG_SYNC;
	while (subchn1->flags & SND_PCM1_FLG_TRIGGER) {	/* still recording? */
		timeout = interruptible_sleep_on_timeout(&subchn1->sleep, 10 * HZ);
		if (signal_pending(current)) {
			err = -EINTR;
			break;
		}
		if ((subchn1->flags & SND_PCM1_FLG_TRIGGER) && !timeout) {
			snd_printd("snd_pcm1_flush_capture: sync timeout\n");
			err = -EAGAIN;
			break;
		}
	}
	subchn1->flags &= ~SND_PCM1_FLG_SYNC;
	snd_pcm1_drain_record(subchn);
	return 0;
}

/*
 *  other things
 */

static unsigned int snd_pcm1_align1(snd_pcm1_subchn_t *subchn1, unsigned int value)
{
	unsigned int tmp;

	if (subchn1->mode & SND_PCM1_MODE_16) {
		tmp = 2;
	} else if (subchn1->mode & SND_PCM1_MODE_24) {
		tmp = (subchn1->voices * 3) - 1;
		value /= tmp;
		value *= tmp;
		return value;
	} else if (subchn1->mode & (SND_PCM1_MODE_32|SND_PCM1_MODE_FLOAT)) {
		tmp = 4;
	} else if (subchn1->mode & SND_PCM1_MODE_FLOAT64) {
		tmp = 8;
	} else {
		tmp = 1;
	}
	value &= ~((subchn1->voices * tmp) - 1);
#if 0
	value &= ~subchn1->hw.align;
#endif
	return value;
}

static unsigned int snd_pcm1_align(snd_pcm_subchn_t * subchn, unsigned int value)
{
	unsigned int tmp;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	if (subchn1->mode & SND_PCM1_MODE_16) {
		tmp = 2;
	} else if (subchn1->mode & SND_PCM1_MODE_24) {
		tmp = (subchn1->voices * 3) - 1;
		value /= tmp;
		value *= tmp;
		if (value < pchn1->hw.align)
			value = ((pchn1->hw.align + 2) / 3) * 3;
		if (!value)
			value = subchn1->voices * 3;
		return value;
	} else if (subchn1->mode & (SND_PCM1_MODE_32|SND_PCM1_MODE_FLOAT)) {
		tmp = 4;
	} else if (subchn1->mode & SND_PCM1_MODE_FLOAT64) {
		tmp = 4;
	} else {
		tmp = 1;
	}
	value &= ~((subchn1->voices * tmp) - 1);
	value &= ~pchn1->hw.align;
	if (!value)
		value = pchn1->hw.align + 1;
	pchn1->hw.ioctl(pchn1->private_data, subchn,
			SND_PCM1_IOCTL_FRAG, (unsigned long *)&value);
	return value;
}

static int snd_pcm1_info(snd_pcm_t * pcm, snd_pcm_info_t * xinfo)
{
	snd_pcm_info_t ginfo, *info;

	info = xinfo;
	info = &ginfo;
	memset(&ginfo, 0, sizeof(ginfo));
	ginfo.type = pcm->card->type;
	ginfo.flags = pcm->info_flags;
	strncpy(ginfo.id, pcm->id, sizeof(ginfo.id) - 1);
	strncpy(ginfo.name, pcm->name, sizeof(ginfo.name) - 1);
	ginfo.playback = pcm->playback.subchn_count - 1;
	ginfo.capture = pcm->capture.subchn_count - 1;
	if (copy_to_user(xinfo, &ginfo, sizeof(ginfo)))
		return -EFAULT;
	return 0;
}

static int snd_pcm1_playback_info(snd_pcm_subchn_t * subchn,
				  snd_pcm_playback_info_t * xinfo)
{
	snd_pcm_playback_info_t ginfo;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	int i;

	if (subchn == NULL)
		return -EIO;
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	memset(&ginfo, 0, sizeof(ginfo));
	ginfo.flags = pchn1->hw.flags & SND_PCM1_HW_COPYMASK;
	ginfo.formats = pchn1->hw.formats;
	ginfo.min_rate = pchn1->hw.min_rate;
	ginfo.max_rate = pchn1->hw.max_rate;
	ginfo.min_channels = 1;
	ginfo.max_channels = pchn1->hw.max_voices;
	if (subchn1) {
		ginfo.buffer_size = subchn1->size;
		if (pchn1->hw.flags & SND_PCM1_HW_8BITONLY) {
			if (subchn1->mode & SND_PCM1_MODE_16)
				ginfo.buffer_size <<= 1;
		}
		if (pchn1->hw.flags & SND_PCM1_HW_16BITONLY) {
			if (!(subchn1->mode & SND_PCM1_MODE_16))
				ginfo.buffer_size >>= 1;
		}
	}
	ginfo.min_fragment_size = 1;
	for (i = 1; i < pchn1->hw.min_fragment; i++)
		ginfo.min_fragment_size <<= 1;
	ginfo.max_fragment_size = ginfo.buffer_size / 2;
	ginfo.fragment_align = pchn1->hw.align;
	ginfo.hw_formats = pchn1->hw.hw_formats;
	ginfo.subdevice = subchn->number;
	if (copy_to_user(xinfo, &ginfo, sizeof(ginfo)))
		return -EFAULT;
	return 0;
}

static int snd_pcm1_capture_info(snd_pcm_subchn_t * subchn,
			         snd_pcm_capture_info_t * xinfo)
{
	snd_pcm_capture_info_t ginfo;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	int i;

	if (subchn == NULL)
		return -EIO;
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	memset(&ginfo, 0, sizeof(ginfo));
	ginfo.flags = pchn1->hw.flags & SND_PCM1_HW_COPYMASK;
	ginfo.formats = pchn1->hw.formats;
	ginfo.min_rate = pchn1->hw.min_rate;
	ginfo.max_rate = pchn1->hw.max_rate;
	ginfo.min_channels = 1;
	ginfo.max_channels = pchn1->hw.max_voices;
	if (subchn1) {
		ginfo.buffer_size = subchn1->size;
		if (pchn1->hw.flags & SND_PCM1_HW_8BITONLY) {
			if (subchn1->mode & SND_PCM1_MODE_16)
				ginfo.buffer_size <<= 1;
		}
		if (pchn1->hw.flags & SND_PCM1_HW_16BITONLY) {
			if (!(subchn1->mode & SND_PCM1_MODE_16))
				ginfo.buffer_size >>= 1;
		}
	}
	ginfo.min_fragment_size = 1;
	for (i = 1; i < pchn1->hw.min_fragment; i++)
		ginfo.min_fragment_size <<= 1;
	ginfo.max_fragment_size = ginfo.buffer_size / 2;
	ginfo.fragment_align = pchn1->hw.align;
	ginfo.hw_formats = pchn1->hw.hw_formats;
	ginfo.subdevice = subchn->number;
	if (copy_to_user(xinfo, &ginfo, sizeof(ginfo)))
		return -EFAULT;
	return 0;
}

static int snd_pcm1_format(snd_pcm_subchn_t * subchn,
			   snd_pcm_format_t * _format, int open_flag)
{
	static unsigned int modes[] = {
		/* SND_PCM_SFMT_MU_LAW */
		SND_PCM1_MODE_U | SND_PCM1_MODE_ULAW,
		/* SND_PCM_SFMT_A_LAW */
		SND_PCM1_MODE_U | SND_PCM1_MODE_ALAW,
		/* SND_PCM_SFMT_IMA_ADPCM */
		SND_PCM1_MODE_U | SND_PCM1_MODE_ADPCM,
		/* SND_PCM_SFMT_U8 */
		SND_PCM1_MODE_U,
		/* SND_PCM_SFMT_S16_LE */
		SND_PCM1_MODE_16,
		/* SND_PCM_SFMT_S16_BE */
		SND_PCM1_MODE_16 | SND_PCM1_MODE_BIG,
		/* SND_PCM_SFMT_S8 */
		0,
		/* SND_PCM_SFMT_U16_LE */
		SND_PCM1_MODE_16 | SND_PCM1_MODE_U,
		/* SND_PCM_SFMT_U16_BE */
		SND_PCM1_MODE_16 | SND_PCM1_MODE_BIG | SND_PCM1_MODE_U,
		/* SND_PCM_SFMT_MPEG */
		SND_PCM1_MODE_MPEG,
		/* SND_PCM_SFMT_GSM */
		SND_PCM1_MODE_GSM,
		/* SND_PCM_SFMT_S24_LE */
		SND_PCM1_MODE_24,
		/* SND_PCM_SFMT_S24_BE */
		SND_PCM1_MODE_24 | SND_PCM1_MODE_BIG,
		/* SND_PCM_SFMT_U24_LE */
		SND_PCM1_MODE_24 | SND_PCM1_MODE_U,
		/* SND_PCM_SFMT_U24_BE */
		SND_PCM1_MODE_24 | SND_PCM1_MODE_U | SND_PCM1_MODE_BIG,
		/* SND_PCM_SFMT_S32_LE */
		SND_PCM1_MODE_32,
		/* SND_PCM_SFMT_S32_BE */
		SND_PCM1_MODE_32 | SND_PCM1_MODE_BIG,
		/* SND_PCM_SFMT_U32_LE */
		SND_PCM1_MODE_32 | SND_PCM1_MODE_U,
		/* SND_PCM_SFMT_U32_BE */
		SND_PCM1_MODE_32 | SND_PCM1_MODE_U | SND_PCM1_MODE_BIG,
		/* SND_PCM_SFMT_FLOAT */
		SND_PCM1_MODE_FLOAT,
		/* SND_PCM_SFMT_FLOAT64 */
		SND_PCM1_MODE_FLOAT64
	};
	int err, tmp;
	unsigned int mode;
	snd_pcm_format_t format;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;

	if (copy_from_user(&format, _format, sizeof(format)))
		return -EFAULT;
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	if (format.rate < pchn1->hw.min_rate)
		format.rate = pchn1->hw.min_rate;
	if (format.rate > pchn1->hw.max_rate)
		format.rate = pchn1->hw.max_rate;
	if (format.channels < 1)
		return -EINVAL;
	if (format.channels > pchn1->hw.max_voices)
		return -EINVAL;
	if (!(pchn1->hw.formats & (1 << format.format))) {
		if (open_flag) {
			/* try find first useable format */
			for (tmp = 0; tmp < 32; tmp++)
				if (pchn1->hw.formats & (1 << tmp)) {
					format.format = tmp;
					break;
				}
			if (tmp >= 32)
				return -EINVAL;
		} else {
			return -EINVAL;
		}
	}
	err = !subchn->capture ?
			snd_pcm1_flush_playback(subchn, 0) :
			snd_pcm1_flush_record(subchn);
	if (err < 0)
		return err;

	mode = subchn1->mode & ~SND_PCM1_MODE_TYPE;
	if (format.format <= SND_PCM_SFMT_FLOAT) {
		mode |= modes[format.format];
	} else {
		return -EINVAL;
	}
	
	subchn1->mode = mode;
	subchn1->rate = format.rate;
	subchn1->voices = format.channels;
	subchn1->neutral_byte = subchn1->mode & (SND_PCM1_MODE_16|SND_PCM1_MODE_24|SND_PCM1_MODE_32|SND_PCM1_MODE_FLOAT) ? 0x00 : 0x80;
	snd_pcm1_new_timer_resolution(subchn);
	snd_pcm1_proc_format(subchn);
	err = pchn1->hw.ioctl(pchn1->private_data, subchn, SND_PCM1_IOCTL_MODE, NULL);
	if (err < 0 && err != -ENXIO)
			return err;
	err = pchn1->hw.ioctl(pchn1->private_data, subchn, SND_PCM1_IOCTL_RATE, NULL);
	if (err < 0 && err != -ENXIO)
			return err;
	err = pchn1->hw.ioctl(pchn1->private_data, subchn, SND_PCM1_IOCTL_VOICES, NULL);
	if (err < 0 && err != -ENXIO)
			return err;
	return 0;
}

static int snd_pcm1_playback_params(snd_pcm_subchn_t * subchn,
				    snd_pcm_playback_params_t * _params)
{
	snd_pcm_playback_params_t params;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	int i, tmp;
	unsigned long flags;

	if (copy_from_user(&params, _params, sizeof(params)))
		return -EFAULT;
	if ((tmp = snd_pcm1_flush_playback(subchn, 0)) < 0)
		return tmp;
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	spin_lock_irqsave(&subchn1->lock, flags);
	for (tmp = i = 1; i < pchn1->hw.min_fragment; i++)
		tmp <<= 1;
	if (params.fragment_size < tmp ||
	    params.fragment_size > subchn1->size / 2) {
		spin_unlock_irqrestore(&subchn1->lock, flags);
		return -EINVAL;
	}
	subchn1->used_size = subchn1->size;
	if (pchn1->hw.flags & SND_PCM1_HW_8BITONLY) {
		if (subchn1->mode & SND_PCM1_MODE_16)
			subchn1->used_size <<= 1;
	}
	if (pchn1->hw.flags & SND_PCM1_HW_16BITONLY) {
		if (!(subchn1->mode & SND_PCM1_MODE_16))
			subchn1->used_size >>= 1;
	}
	subchn1->block_size = snd_pcm1_align(subchn, params.fragment_size);
	subchn1->blocks = subchn1->used_size / subchn1->block_size;
	subchn1->used_size = subchn1->blocks * subchn1->block_size;
	if (params.fragments_max < 0) {
		subchn1->blocks_max = subchn1->blocks + params.fragments_max;
		if (subchn1->blocks_max < 1)
			subchn1->blocks_max = 1;
	} else {
		subchn1->blocks_max = params.fragments_max;
		if (subchn1->blocks_max > subchn1->blocks - 1)
			subchn1->blocks_max = subchn1->blocks - 1;
	}
	subchn1->blocks_room = params.fragments_room;
	if (subchn1->blocks_room > subchn1->blocks / 2)
		subchn1->blocks_room = subchn1->blocks / 2;
	snd_pcm1_new_timer_resolution(subchn);
	spin_unlock_irqrestore(&subchn1->lock, flags);
#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("playback_params: used_size = %i, blocks = %i, blocks_size = %i, blocks_max = %i, blocks_room = %i\n", subchn1->used_size, subchn1->blocks, subchn1->block_size, subchn1->blocks_max, subchn1->blocks_room);
#endif
	return 0;
}

static int snd_pcm1_capture_params(snd_pcm_subchn_t * subchn,
				   snd_pcm_capture_params_t * _params)
{
	snd_pcm_capture_params_t params;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	int i, tmp;
	unsigned long flags;

	if (copy_from_user(&params, _params, sizeof(params)))
		return -EFAULT;
	if ((tmp = snd_pcm1_flush_record(subchn)) < 0)
		return tmp;
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	spin_lock_irqsave(&subchn1->lock, flags);
	for (tmp = i = 1; i < pchn1->hw.min_fragment; i++)
		tmp <<= 1;
	subchn1->used_size = subchn1->size;
	if (pchn1->hw.flags & SND_PCM1_HW_8BITONLY) {
		if (subchn1->mode & SND_PCM1_MODE_16)
			subchn1->used_size <<= 1;
	}
	if (pchn1->hw.flags & SND_PCM1_HW_16BITONLY) {
		if (!(subchn1->mode & SND_PCM1_MODE_16))
			subchn1->used_size >>= 1;
	}
	if (params.fragment_size < tmp ||
	    params.fragment_size > subchn1->size / 2) {
		spin_unlock_irqrestore(&subchn1->lock, flags);
		return -EINVAL;
	}
	subchn1->block_size = snd_pcm1_align(subchn, params.fragment_size);
	subchn1->frag_size = subchn1->block_size;
	subchn1->blocks = subchn1->used_size / subchn1->block_size;
	subchn1->used_size = subchn1->blocks * subchn1->block_size;
	subchn1->blocks_min = params.fragments_min;
	if (subchn1->blocks_min > subchn1->blocks / 2)
		subchn1->blocks_min = subchn1->blocks / 2;
	if (subchn1->blocks_min < 1)
		subchn1->blocks_min = 1;
	snd_pcm1_new_timer_resolution(subchn);
	spin_unlock_irqrestore(&subchn1->lock, flags);
#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("capture_params: used_size = %i, blocks = %i, blocks_size = %i, blocks_min = %i\n", subchn1->used_size, subchn1->blocks, subchn1->block_size, subchn1->blocks_min);
#endif
	return 0;
}

static void snd_pcm1_get_time(snd_pcm1_subchn_t * subchn1,
                              struct timeval *time, int diff_bytes)
{
	unsigned int bps;	/* bytes per second */

	if (subchn1->time.tv_sec == 0) {
		time->tv_sec = time->tv_usec = 0;
	} else {
		if ((bps = snd_pcm1_bytes_per_second(subchn1)) == 0)
			return;
		do_gettimeofday(time);
		time->tv_sec += diff_bytes / (int) bps;
		diff_bytes %= (int) bps;
		/* precision should be better :-((, but we have only 2^31 range */
		if (diff_bytes > 214748 || diff_bytes < -214748) {
			time->tv_usec += (((1000L * diff_bytes) +
					 (int) (bps >> 1)) / (int) bps) * 1000L;
		} else {
			time->tv_usec += (((10000L * diff_bytes) +
			                 (int) (bps >> 1)) / (int) bps) * 100L;
		}
		if (time->tv_usec < 0) {
			time->tv_usec += 1000000UL;
			time->tv_sec--;
		} else if (time->tv_usec >= 1000000UL) {
			time->tv_usec -= 1000000UL;
			time->tv_sec++;
		}
	}
}

static int snd_pcm1_playback_status(snd_pcm_subchn_t * subchn,
				    snd_pcm_playback_status_t * _status)
{
	snd_pcm_playback_status_t status;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	unsigned long flags;
	int ptr;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	memset(&status, 0, sizeof(status));
	/* fill static variables at first */
	status.rate = subchn1->real_rate;
	status.fragments = subchn1->blocks;
	status.fragment_size = subchn1->block_size;
	/* spinlock block */
	spin_lock_irqsave(&subchn1->lock, flags);
	if ((subchn1->flags & SND_PCM1_FLG_TRIGGER) ||
	    ((subchn1->flags & SND_PCM1_FLG_PAUSE) &&
	     (pchn1->hw.flags & SND_PCM1_HW_PAUSE))) {
		if (pchn1->hw.flags & SND_PCM1_HW_BLOCKPTR) {
			ptr = pchn1->hw.pointer(pchn1->private_data, subchn, subchn1->block_size);
		} else {
			ptr = pchn1->hw.pointer(pchn1->private_data, subchn, subchn1->used_size);
			ptr -= subchn1->tail * subchn1->block_size;
		}
		if (ptr < 0)
			ptr = 0;
		if (ptr > subchn1->block_size - 1)
			ptr = subchn1->block_size - 1;
	} else {
		ptr = 0;
	}
	status.count = (((subchn1->blocks_max + 1) - subchn1->used) *
					subchn1->block_size) - subchn1->frag_size;
	if (status.count < 0)
		status.count = 0;
	status.queue = ((subchn1->used * subchn1->block_size) +
					subchn1->frag_size) - ptr;
	status.underrun = subchn1->xruns & 0x7fffffff;
	subchn1->xruns = 0;
	status.scount = subchn1->processed_bytes + ptr;
	spin_unlock_irqrestore(&subchn1->lock, flags);
	if ((subchn1->flags & SND_PCM1_FLG_TIME) &&
	    !(subchn1->flags & SND_PCM1_FLG_PAUSE)) {
		/* we know about current time and about bytes in queue */
		snd_pcm1_get_time(subchn1, &status.time, status.queue);
		status.stime = subchn1->time;
	} else {
		status.time.tv_sec = status.time.tv_usec =
		    status.stime.tv_sec = status.stime.tv_usec = 0;
	}
	if (copy_to_user(_status, &status, sizeof(snd_pcm_playback_status_t)))
		return -EFAULT;
	return 0;
}

static int snd_pcm1_capture_status(snd_pcm_subchn_t * subchn,
				   snd_pcm_capture_status_t * _status)
{
	snd_pcm_capture_status_t status;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	unsigned long flags;
	int ptr;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	memset(&status, 0, sizeof(status));
	/* fill static variables at first */
	status.rate = subchn1->real_rate;
	status.fragments = subchn1->blocks;
	status.fragment_size = subchn1->block_size;
	/* spinlock block */
	spin_lock_irqsave(&subchn1->lock, flags);
	if (subchn1->flags & SND_PCM1_FLG_TRIGGER) {
		if (pchn1->hw.flags & SND_PCM1_HW_BLOCKPTR) {
			ptr = pchn1->hw.pointer(pchn1->private_data, subchn, subchn1->block_size);
		} else {
			ptr = pchn1->hw.pointer(pchn1->private_data, subchn, subchn1->used_size);
			ptr -= subchn1->tail * subchn1->block_size;
		}
		if (ptr < 0)
			ptr = 0;
		if (ptr > subchn1->block_size - 1)
			ptr = subchn1->block_size - 1;
	} else {
		ptr = 0;
	}
	if (!subchn1->used) {
		status.count = 0;
	} else {
		status.count = (subchn1->used - 1) * subchn1->block_size +
			       subchn1->frag_size;
	}
	status.free = ((subchn1->blocks - subchn1->used) * subchn1->block_size) - ptr;
	status.overrun = subchn1->xruns & 0x7fffffff;
	subchn1->xruns = 0;
	status.scount = subchn1->processed_bytes + ptr;
	if (pchn1->hw.flags & SND_PCM1_HW_OVERRANGE)
		status.overrange = subchn1->overrange;
	spin_unlock_irqrestore(&subchn1->lock, flags);
	if (subchn1->flags & SND_PCM1_FLG_TIME) {
		snd_pcm1_get_time(subchn1, &status.time, -(status.count + ptr));
		status.stime = subchn1->time;
	} else {
		status.time.tv_sec = status.time.tv_usec =
		    status.stime.tv_sec = status.stime.tv_usec = 0;
	}
	if (copy_to_user(_status, &status, sizeof(snd_pcm_playback_status_t)))
		return -EFAULT;
	return 0;
}

static int snd_pcm1_playback_pause1_enable(snd_pcm_subchn_t * subchn)
{
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	unsigned int used, block_size, idx;
	unsigned char *buffer;
	
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;

	subchn1->flags &= ~SND_PCM1_FLG_TRIGGER;
	if (subchn1->tail == 0)
		return 0;

	/* ok. create correct block order for auto-init DMA transfers */
	used = subchn1->used;
	if (subchn1->frag_size > 0)
		used++;
	block_size = subchn1->block_size;
	if (subchn1->tail + used >= subchn1->blocks) {
		buffer = snd_vmalloc(subchn1->size);
		if (buffer == NULL) {
			snd_pcm1_drain_playback(subchn);
			return -ENOMEM;
		}
		for (idx = 0; idx < used; idx++) {
			pchn1->hw.dma_move(pchn1->private_data,
					   subchn,
					   buffer,
					   idx * block_size,
					   subchn1->buffer,
					   ((subchn1->tail + idx) % subchn1->blocks) * block_size,
					   block_size);
		}
		pchn1->hw.dma_move(pchn1->private_data,
				   subchn,
				   subchn1->buffer,
				   0,
				   buffer,
				   0,
				   used * block_size);
		snd_vfree(buffer);
	} else {
		for (idx = 0; idx < used; idx++)
			pchn1->hw.dma_move(pchn1->private_data,
					   subchn,
					   subchn1->buffer,
					   idx * block_size,
					   subchn1->buffer,
					   ((subchn1->tail + idx) % subchn1->blocks) * block_size,
					   block_size);
	}
	subchn1->head = subchn1->used;
	subchn1->tail = 0;
	return 0;
}

static int snd_pcm1_playback_pause(snd_pcm_subchn_t * subchn, int enable)
{
	int err = 0;
	unsigned long flags;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;

	spin_lock_irqsave(&subchn1->lock, flags);
	if (enable) {
		if (subchn1->flags & SND_PCM1_FLG_PAUSE) {
			spin_unlock_irqrestore(&subchn1->lock, flags);
			return 0;
		}
		subchn1->flags |= SND_PCM1_FLG_PAUSE;
		if (pchn1->hw.flags & SND_PCM1_HW_PAUSE) {
			err = pchn1->hw.ioctl(pchn1->private_data,
					      subchn,
					      SND_PCM1_IOCTL_PAUSE,
					      SND_PCM1_IOCTL_TRUE);
		} else {
			pchn1->hw.trigger(pchn1->private_data, subchn, 0);
			spin_unlock_irqrestore(&subchn1->lock, flags);
			return snd_pcm1_playback_pause1_enable(subchn);
		}
	} else {
		if (!(subchn1->flags & SND_PCM1_FLG_PAUSE)) {
			spin_unlock_irqrestore(&subchn1->lock, flags);
			return 0;
		}
		subchn1->flags &= ~SND_PCM1_FLG_PAUSE;
		if (pchn1->hw.flags & SND_PCM1_HW_PAUSE) {
			err = pchn1->hw.ioctl(pchn1->private_data,
					      subchn,
					      SND_PCM1_IOCTL_PAUSE,
					      SND_PCM1_IOCTL_FALSE);
		} else {
			spin_unlock_irqrestore(&subchn1->lock, flags);
			snd_pcm1_trigger_playback(subchn);
			return 0;
		}
	}
	spin_unlock_irqrestore(&subchn1->lock, flags);
	return err;
}

static int snd_pcm1_set_time(snd_pcm_subchn_t * subchn, long * _arg)
{
	int tmp;
	snd_pcm1_subchn_t *subchn1;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	tmp = snd_ioctl_in(_arg);
	if (tmp)
		subchn1->flags |= SND_PCM1_FLG_TIME;
	else
		subchn1->flags &= ~SND_PCM1_FLG_TIME;
	snd_ioctl_out(_arg, tmp != 0);
	return 0;
}

void snd_pcm1_subchannel_free(void *private_data)
{
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *)private_data;

	if (subchn1) {
		if (subchn1->private_free)
			subchn1->private_free(subchn1->private_data);
		snd_kfree(private_data);
	}
}

static int snd_pcm1_open_card(snd_pcm_t * pcm, struct file *file,
			      snd_pcm_file_t ** r_pcm_file)
{
	mm_segment_t fs;
	int res = 0;
	snd_pcm_file_t *pcm_file;
	snd_pcm_format_t format;
	snd_pcm_playback_params_t pparams;
	snd_pcm_capture_params_t rparams;
	snd_pcm_subchn_t *playback_subchn = NULL;
	snd_pcm1_subchn_t *playback_subchn1 = NULL;
	snd_pcm1_channel_t *playback_pchn1 = NULL;
	snd_pcm_subchn_t *capture_subchn = NULL;
	snd_pcm1_subchn_t *capture_subchn1 = NULL;
	snd_pcm1_channel_t *capture_pchn1 = NULL;

	*r_pcm_file = NULL;
	res = snd_pcm_open(pcm,
			   file->f_mode & FMODE_WRITE,
			   file->f_mode & FMODE_READ,
			   &pcm_file);
	if (res < 0)
		return res;
	if (file->f_mode & FMODE_WRITE) {
		playback_subchn = pcm_file->playback;
		playback_subchn1 = (snd_pcm1_subchn_t *) snd_kcalloc(sizeof(*playback_subchn1), GFP_KERNEL);
		if (playback_subchn1 == NULL) {
			res = -ENOMEM;
			goto __end;
		}
		init_waitqueue_head(&playback_subchn1->sleep);
		playback_subchn->private_data = playback_subchn1;
		playback_subchn->private_free = snd_pcm1_subchannel_free;
		playback_pchn1 = (snd_pcm1_channel_t *) playback_subchn->pchn->private_data;
		snd_pcm1_clear_subchannel(playback_subchn);
	}
	if (file->f_mode & FMODE_READ) {
		capture_subchn = pcm_file->capture;
		capture_subchn1 = (snd_pcm1_subchn_t *) snd_kcalloc(sizeof(*capture_subchn1), GFP_KERNEL);
		if (capture_subchn1 == NULL) {
			res = -ENOMEM;
			goto __end;
		}
		init_waitqueue_head(&capture_subchn1->sleep);
		capture_subchn->private_data = capture_subchn1;
		capture_subchn->private_free = snd_pcm1_subchannel_free;
		capture_pchn1 = (snd_pcm1_channel_t *) capture_subchn->pchn->private_data;
		snd_pcm1_clear_subchannel(capture_subchn);
	}
	memset(&format, 0, sizeof(format));
	format.format = SND_PCM_SFMT_MU_LAW;
	format.rate = SND_PCM_DEFAULT_RATE;
	format.channels = 1;
	if (file->f_mode & FMODE_WRITE) {
		if ((res = playback_pchn1->hw.open(
				playback_pchn1->private_data,
				playback_subchn)) < 0)
			goto __end;
		playback_subchn1->flags |= SND_PCM1_FLG_ENABLE;
		playback_subchn1->ack = snd_pcm1_interrupt_playback;
		fs = snd_enter_user();
		if ((res = snd_pcm1_format(playback_subchn, &format, 1)) < 0) {
			snd_printk("Oops.. Playback format setup failed!!!\n");
			snd_leave_user(fs);
			playback_pchn1->hw.close(playback_pchn1->private_data,
						 playback_subchn);
			goto __playback_end;
		}
		memset(&pparams, 0, sizeof(pparams));
		pparams.fragment_size = 1024;
		pparams.fragments_max = -1;
		pparams.fragments_room = 1;
		if ((res = snd_pcm1_playback_params(playback_subchn, &pparams)) < 0) {
			snd_printk("Oops.. Playback parameters setup failed!!!\n");
			snd_leave_user(fs);
			goto __playback_end;
		}
		snd_leave_user(fs);
		playback_subchn1->flags |= SND_PCM1_FLG_ENABLE;
	}
	if (file->f_mode & FMODE_READ) {
		if ((res = capture_pchn1->hw.open(
					capture_pchn1->private_data,
					capture_subchn)) < 0)
			goto __playback_end;
		capture_subchn1->flags |= SND_PCM1_FLG_ENABLE;
		capture_subchn1->ack = snd_pcm1_interrupt_capture;
		fs = snd_enter_user();
		if ((res = snd_pcm1_format(capture_subchn,
					   &format, 1)) < 0) {
			snd_printk("Oops.. Capture format setup failed!!!\n");
			snd_leave_user(fs);
			goto __capture_end;
		}
		memset(&rparams, 0, sizeof(rparams));
		rparams.fragment_size = 1024;
		rparams.fragments_min = 1;
		if ((res = snd_pcm1_capture_params(capture_subchn,
						   &rparams)) < 0) {
			snd_printk("Oops.. Capture parameters setup failed!!!\n");
			snd_leave_user(fs);
			goto __capture_end;
		}
		snd_leave_user(fs);
		capture_subchn1->flags |= SND_PCM1_FLG_ENABLE;
	}
#if 0
	printk("pcm1 open - done...\n");
#endif
	*r_pcm_file = pcm_file;
	return 0;

      __capture_end:
      	if (file->f_mode & FMODE_READ)
		capture_pchn1->hw.close(capture_pchn1->private_data,
					capture_subchn);

      __playback_end:
	if (file->f_mode & FMODE_WRITE)
		playback_pchn1->hw.close(playback_pchn1->private_data,
					 playback_subchn);

      __end:
      	snd_pcm_release(pcm_file);
      	return res;
}

static int snd_pcm1_close_card(snd_pcm_file_t * pcm_file, struct file *file)
{
	snd_pcm_subchn_t *subchn;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;

	if (pcm_file == NULL)
		return -EINVAL;
	if (pcm_file->playback) {
		subchn = pcm_file->playback;
		subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
		pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
		pchn1->hw.trigger(pchn1->private_data, subchn, 0); /* for sure */
		pchn1->hw.close(pchn1->private_data, subchn); /* for sure */
	}
	if (pcm_file->capture) {
		subchn = pcm_file->capture;
		subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
		pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
		snd_pcm1_drain_record(subchn);	/* and capture, data can't be read, so flush isn't needed */
		pchn1->hw.trigger(pchn1->private_data, subchn, 0); /* for sure */
		pchn1->hw.close(pchn1->private_data, subchn);
	}
	return snd_pcm_release(pcm_file);
#if 0
	printk("release pcm: done\n");
#endif
}

static int snd_pcm1_open(unsigned short minor, int cardnum, int device, struct file *file)
{
	int res;
	snd_pcm_t *pcm;
	snd_pcm_file_t *pcm_file;

	pcm = snd_pcm_devices[(cardnum * SND_PCM_DEVICES) + device];
	if (!pcm)
		return -ENODEV;
	down(&pcm->open_mutex);
	if ((res = snd_pcm1_open_card(pcm, file, &pcm_file)) < 0) {
		up(&pcm->open_mutex);
		return res;
	}
	file->private_data = pcm_file;
	MOD_INC_USE_COUNT;
	pcm->card->use_inc(pcm->card);
	up(&pcm->open_mutex);
	return 0;
}

static int snd_pcm1_release(unsigned short minor, int cardnum,
			    int device, struct file *file)
{
	snd_pcm_t *pcm;
	snd_pcm_file_t *pcm_file;

	if (file->private_data) {
		pcm_file = (snd_pcm_file_t *) file->private_data;
		if (pcm_file->playback) {
			pcm = pcm_file->playback->pcm;
			snd_pcm1_flush_playback(pcm_file->playback, 1); /* synchronize playback */
		} else {
			pcm = pcm_file->capture->pcm;
		}
		down(&pcm->open_mutex);
		snd_pcm1_close_card(pcm_file, file);
		up(&pcm->open_mutex);
		pcm->card->use_dec(pcm->card);
	}
	MOD_DEC_USE_COUNT;
	return 0;
}

static int snd_pcm1_ioctl(struct file *file,
			  unsigned int cmd, unsigned long arg)
{
	snd_pcm_file_t *pcm_file;

	if (((cmd >> 8) & 0xff) != 'A')
		return -EIO;
	pcm_file = (snd_pcm_file_t *) file->private_data;

	switch (cmd) {
	case SND_PCM_IOCTL_PVERSION:
		return snd_ioctl_out((long *) arg, SND_PCM_VERSION);
	case SND_PCM_IOCTL_INFO:
		return snd_pcm1_info(pcm_file->pcm, (snd_pcm_info_t *) arg);
	}
	if (file->f_mode & FMODE_WRITE) {
		switch (cmd) {
		case SND_PCM_IOCTL_PLAYBACK_INFO:
			return snd_pcm1_playback_info(pcm_file->playback, (snd_pcm_playback_info_t *) arg);
		case SND_PCM_IOCTL_PLAYBACK_FORMAT:
			return snd_pcm1_format(pcm_file->playback, (snd_pcm_format_t *) arg, 0);
		case SND_PCM_IOCTL_PLAYBACK_PARAMS:
			return snd_pcm1_playback_params(pcm_file->playback, (snd_pcm_playback_params_t *) arg);
		case SND_PCM_IOCTL_PLAYBACK_STATUS:
			return snd_pcm1_playback_status(pcm_file->playback, (snd_pcm_playback_status_t *) arg);
		case SND_PCM_IOCTL_DRAIN_PLAYBACK:
			return snd_pcm1_drain_playback(pcm_file->playback);
		case SND_PCM_IOCTL_FLUSH_PLAYBACK:
			return snd_pcm1_flush_playback(pcm_file->playback, 0);
		case SND_PCM_IOCTL_PLAYBACK_PAUSE:
			return snd_pcm1_playback_pause(pcm_file->playback, snd_ioctl_in((long *) arg));
		case SND_PCM_IOCTL_PLAYBACK_TIME:
			return snd_pcm1_set_time(pcm_file->playback, (long *) arg);
			return 0;
		}
	}
	if (file->f_mode & FMODE_READ) {
		switch (cmd) {
		case SND_PCM_IOCTL_CAPTURE_INFO:
			return snd_pcm1_capture_info(pcm_file->capture, (snd_pcm_capture_info_t *) arg);
		case SND_PCM_IOCTL_CAPTURE_FORMAT:
			return snd_pcm1_format(pcm_file->capture, (snd_pcm_format_t *) arg, 0);
		case SND_PCM_IOCTL_CAPTURE_PARAMS:
			return snd_pcm1_capture_params(pcm_file->capture, (snd_pcm_capture_params_t *) arg);
		case SND_PCM_IOCTL_CAPTURE_STATUS:
			return snd_pcm1_capture_status(pcm_file->capture, (snd_pcm_capture_status_t *) arg);
		case SND_PCM_IOCTL_FLUSH_CAPTURE:
			return snd_pcm1_flush_record(pcm_file->capture);
		case SND_PCM_IOCTL_CAPTURE_TIME:
			return snd_pcm1_set_time(pcm_file->capture, (long *) arg);
		}
	}

	snd_printd("pcm: unknown command = 0x%x\n", cmd);
	return -ENXIO;
}

static int snd_pcm1_control_ioctl(snd_card_t * card, snd_control_t * control,
		                  unsigned int cmd, unsigned long arg)
{
	unsigned int tmp;
	snd_pcm_t *pcm;
	snd_pcm_subchn_t *subchn;

	tmp = card->number << 2;
	pcm = snd_pcm_devices[tmp + control->pcm_device];
	switch (cmd) {
	case SND_CTL_IOCTL_PCM_INFO:
		return snd_pcm1_info(pcm, (snd_pcm_info_t *) arg);
	case SND_CTL_IOCTL_PCM_PLAYBACK_INFO:
		for (subchn = pcm->playback.subchn; subchn; subchn = subchn->next)
			if (subchn->number == control->pcm_subdevice)
				break;
		if (subchn == NULL)
			return -EINVAL;
		return snd_pcm1_playback_info(subchn, (snd_pcm_playback_info_t *) arg);
	case SND_CTL_IOCTL_PCM_CAPTURE_INFO:
		for (subchn = pcm->capture.subchn; subchn; subchn = subchn->next)
			if (subchn->number == control->pcm_subdevice)
				break;
		if (subchn == NULL)
			return -EINVAL;
		return snd_pcm1_capture_info(pcm->capture.subchn, (snd_pcm_capture_info_t *) arg);
	}
	return -EAGAIN;
}

static long snd_pcm1_read(struct file *file, char *buf, long count)
{
	snd_pcm_file_t *pcm_file;

	pcm_file = (snd_pcm_file_t *) file->private_data;
	if (pcm_file == NULL)
		return -EIO;
	if (!(file->f_mode & FMODE_READ) || pcm_file->capture == NULL)
		return -EIO;
	return snd_pcm1_buffer_to_user(file, pcm_file->capture, buf, count);
}

static long snd_pcm1_write(struct file *file, const char *buf, long count)
{
	snd_pcm_file_t *pcm_file;
	long result;

	pcm_file = (snd_pcm_file_t *) file->private_data;
	if (pcm_file == NULL)
		return -EIO;
	if (!(file->f_mode & FMODE_WRITE) || pcm_file->playback == NULL)
		return -EIO;
	up(&file->f_dentry->d_inode->i_sem);
	result = snd_pcm1_user_to_buffer(file, pcm_file->playback, buf, count);
	down(&file->f_dentry->d_inode->i_sem);
	return result;
}

static unsigned int snd_pcm1_poll(struct file *file, poll_table * wait)
{
	snd_pcm_file_t *pcm_file;
	unsigned int mask;
	snd_pcm_subchn_t *playback_subchn = NULL;
	snd_pcm1_subchn_t *playback_subchn1 = NULL;
	snd_pcm1_channel_t *playback_pchn1 = NULL;
	snd_pcm_subchn_t *capture_subchn = NULL;
	snd_pcm1_subchn_t *capture_subchn1 = NULL;
	snd_pcm1_channel_t *capture_pchn1 = NULL;

	pcm_file = (snd_pcm_file_t *) file->private_data;
	if (!pcm_file)
		return 0;

	if (file->f_mode & FMODE_READ) {
		capture_subchn = pcm_file->capture;
		capture_subchn1 = (snd_pcm1_subchn_t *) capture_subchn->private_data;
		capture_pchn1 = (snd_pcm1_channel_t *) capture_subchn->pchn->private_data;
		snd_pcm1_trigger_capture(capture_subchn);
		poll_wait(file, &capture_subchn1->sleep, wait);
	}
	if (file->f_mode & FMODE_WRITE) {
		playback_subchn = pcm_file->playback;
		playback_subchn1 = (snd_pcm1_subchn_t *) playback_subchn->private_data;
		playback_pchn1 = (snd_pcm1_channel_t *) playback_subchn->pchn->private_data;
		poll_wait(file, &playback_subchn1->sleep, wait);
	}
	mask = 0;
	if (file->f_mode & FMODE_READ) {
		if (snd_pcm1_capture_ok(capture_subchn1))
			mask |= POLLIN | POLLRDNORM;
	}
	if (file->f_mode & FMODE_WRITE) {
		if (snd_pcm1_playback_ok(playback_subchn1))
			mask |= POLLOUT | POLLWRNORM;
	}
	return mask;
}

/*
 *  /proc interface
 */

void snd_pcm1_proc_format(snd_pcm_subchn_t *subchn)
{
	snd_pcm_format_t format;
	unsigned int mode;
	snd_pcm1_subchn_t *subchn1;
	
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	memset(&format, 0, sizeof(format));
	mode = subchn1->mode;
	if (mode & SND_PCM1_MODE_GSM) {
		format.format = SND_PCM_SFMT_GSM;
	} else if (mode & SND_PCM1_MODE_MPEG) {
		format.format = SND_PCM_SFMT_MPEG;
	} else if (mode & SND_PCM1_MODE_ADPCM) {
		format.format = SND_PCM_SFMT_IMA_ADPCM;
	} else if (mode & SND_PCM1_MODE_ALAW) {
		format.format = SND_PCM_SFMT_A_LAW;
	} else if (mode & SND_PCM1_MODE_ULAW) {
		format.format = SND_PCM_SFMT_MU_LAW;
	} else if (mode & SND_PCM1_MODE_FLOAT) {
		format.format = SND_PCM_SFMT_FLOAT;
	} else if (mode & SND_PCM1_MODE_FLOAT64) {
		format.format = SND_PCM_SFMT_FLOAT64;
	} else if (mode & SND_PCM1_MODE_U) {
		if (mode & SND_PCM1_MODE_16) {
			format.format = mode & SND_PCM1_MODE_BIG ?
						SND_PCM_SFMT_U16_BE :
						SND_PCM_SFMT_U16_LE;
		} else if (mode & SND_PCM1_MODE_24) {
			format.format = mode & SND_PCM1_MODE_BIG ?
						SND_PCM_SFMT_U24_BE :
						SND_PCM_SFMT_U24_LE;
		} else if (mode & SND_PCM1_MODE_32) {
			format.format = mode & SND_PCM1_MODE_BIG ?
						SND_PCM_SFMT_U32_BE :
						SND_PCM_SFMT_U32_LE;
		} else {
			format.format = SND_PCM_SFMT_U8;
		}
	} else {
		if (mode & SND_PCM1_MODE_16) {
			format.format = mode & SND_PCM1_MODE_BIG ?
						SND_PCM_SFMT_S16_BE :
						SND_PCM_SFMT_S16_LE;
		} else if (mode & SND_PCM1_MODE_24) {
			format.format = mode & SND_PCM1_MODE_BIG ?
						SND_PCM_SFMT_S24_BE :
						SND_PCM_SFMT_S24_LE;
		} else if (mode & SND_PCM1_MODE_32) {
			format.format = mode & SND_PCM1_MODE_BIG ?
						SND_PCM_SFMT_S32_BE :
						SND_PCM_SFMT_S32_LE;
		} else {
			format.format = mode & SND_PCM_SFMT_S8;
		}
	}
	format.rate = subchn1->rate;
	format.channels = subchn1->voices;
	snd_pcm_proc_format(subchn, &format);
}

static const char *snd_pcm1_proc_get_format(unsigned int mode)
{
	if (mode & SND_PCM1_MODE_GSM)
		return "GSM";
	if (mode & SND_PCM1_MODE_MPEG)
		return "MPEG";
	if (mode & SND_PCM1_MODE_ADPCM)
		return "Ima-ADPCM";
	if (mode & SND_PCM1_MODE_ALAW)
		return "A-Law";
	if (mode & SND_PCM1_MODE_ULAW)
		return "Mu-Law";
	if (mode & SND_PCM1_MODE_FLOAT)
		return "Float";
	if (mode & SND_PCM1_MODE_FLOAT64)
		return "Float64";
	if (mode & SND_PCM1_MODE_U) {
		if (mode & SND_PCM1_MODE_16) {
			return mode & SND_PCM1_MODE_BIG ? "Unsigned 16-bit Big Endian" : "Unsigned 16-bit Little Endian";
		} else if (mode & SND_PCM1_MODE_24) {
			return mode & SND_PCM1_MODE_BIG ? "Unsigned 24-bit Big Endian" : "Unsigned 24-bit Little Endian";
		} else if (mode & SND_PCM1_MODE_32) {
			return mode & SND_PCM1_MODE_BIG ? "Unsigned 32-bit Big Endian" : "Unsigned 32-bit Little Endian";
		} else {
			return "Unsigned 8-bit";
		}
	} else {
		if (mode & SND_PCM1_MODE_16) {
			return mode & SND_PCM1_MODE_BIG ? "Signed 16-bit Big Endian" : "Signed 16-bit Little Endian";
		} else if (mode & SND_PCM1_MODE_24) {
			return mode & SND_PCM1_MODE_BIG ? "Signed 24-bit Big Endian" : "Signed 24-bit Little Endian";
		} else if (mode & SND_PCM1_MODE_32) {
			return mode & SND_PCM1_MODE_BIG ? "Signed 32-bit Big Endian" : "Signed 32-bit Little Endian";
		} else {
			return "Signed 8-bit";
		}
	}
	return "Unknown";
}

static void snd_pcm1_proc_info_playback_read(snd_info_buffer_t * buffer, void *private_data)
{
	snd_pcm_subchn_t *subchn;
	snd_pcm1_subchn_t *subchn1;

	subchn = (snd_pcm_subchn_t *) private_data;
	snd_iprintf(buffer, "%s\n\n", subchn->pcm->name);
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	if (subchn1) {
		snd_iprintf(buffer,
			    "Playback\n"
			    "  Mode           : %s%s\n"
			    "  Format         : %s\n"
			    "  Rate           : %dHz [requested %dHz]\n"
			    "  Channels       : %d\n"
			    "  Buffer size    : %d\n"
			    "  Buffer used    : %d\n"
			    "  Fragments      : %d\n"
			    "  Fragment size  : %d\n",
#ifdef CONFIG_SND_OSSEMUL
				subchn->oss ? "OSS compatible" : "native",
#else
				"native",
#endif
				subchn1->flags & SND_PCM1_FLG_MMAP ? " [mmap]" : "",
				snd_pcm1_proc_get_format(subchn1->mode),
				subchn1->real_rate,
			   	subchn1->rate,
				subchn1->voices,
				subchn1->size,
				subchn1->used_size,
				subchn1->blocks,
				subchn1->block_size);
#ifdef CONFIG_SND_OSSEMUL
		if (!subchn->oss) {
#else
		{
#endif
			snd_iprintf(buffer,
				"  Fragments room : %d\n"
				"  Fragments max  : %d\n",
				subchn1->blocks_room,
				subchn1->blocks_max);
		}
		snd_iprintf(buffer,
				"  Underruns      : %d\n"
				"  Total underruns: %d\n",
				subchn1->xruns,
				subchn1->total_xruns);
	} else {
		snd_iprintf(buffer, "Playback is not active.\n");
	}
}

static void snd_pcm1_proc_info_capture_read(snd_info_buffer_t * buffer, void *private_data)
{
	snd_pcm_subchn_t *subchn;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;

	subchn = (snd_pcm_subchn_t *) private_data;
	snd_iprintf(buffer, "%s\n\n", subchn->pcm->name);
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	if (subchn1) {
		pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
		snd_iprintf(buffer,
				"Capture\n"
				"  Mode           : %s%s\n"
				"  Format         : %s\n"
				"  Rate           : %dHz [requested %dHz]\n"
				"  Channels       : %d\n"
				"  Buffer size    : %d\n"
				"  Buffer used    : %d\n"
				"  Fragments      : %d\n"
				"  Fragment size  : %d\n",
#ifdef CONFIG_SND_OSSEMUL
					subchn->oss ? "OSS compatible" : "native",
#else
					"native",
#endif
					subchn1->flags & SND_PCM1_FLG_MMAP ? " [mmap]" : "",
					snd_pcm1_proc_get_format(subchn1->mode),
					subchn1->real_rate,
					subchn1->rate,
					subchn1->voices,
					subchn1->size,
					subchn1->used_size,
					subchn1->blocks,
					subchn1->block_size);
#ifdef CONFIG_SND_OSSEMUL
		if (!subchn->oss) {
#else
		{
#endif
			snd_iprintf(buffer,
				    "  Fragments min  : %d\n",
					subchn1->blocks_min);
		}
		snd_iprintf(buffer,
			    "  Overruns       : %d\n"
			    "  Total overruns : %d\n",
				subchn1->xruns,
				subchn1->total_xruns);
		if (pchn1->hw.flags & SND_PCM1_HW_OVERRANGE)
			snd_iprintf(buffer,
				    "  Overrange      : %d\n",
				    subchn1->overrange);
	} else {
		snd_iprintf(buffer, "Capture is not active.\n");
	}
}

static void snd_pcm1_proc_init(snd_pcm_t * pcm)
{
	char name[16];
	snd_info_entry_t *entry;
	snd_pcm_subchn_t *subchn;

	for (subchn = pcm->playback.subchn; subchn; subchn = subchn->next) {
		sprintf(name, "pcmD%dS%dp", pcm->device, subchn->number);
		entry = snd_info_create_entry(pcm->card, name);
		if (entry) {
			entry->private_data = subchn;
			entry->t.text.read_size = 1024;
			entry->t.text.read = snd_pcm1_proc_info_playback_read;
			if (snd_info_register(entry) < 0) {
				snd_info_free_entry(entry);
				entry = NULL;
			}
			subchn->proc_entry1 = entry;
		}
	}
	for (subchn = pcm->capture.subchn; subchn; subchn = subchn->next) {
		sprintf(name, "pcmD%dS%dc", pcm->device, subchn->number);
		entry = snd_info_create_entry(pcm->card, name);
		if (entry) {
			entry->private_data = subchn;
			entry->t.text.read_size = 1024;
			entry->t.text.read = snd_pcm1_proc_info_capture_read;
			if (snd_info_register(entry) < 0) {
				snd_info_free_entry(entry);
				entry = NULL;
			}
			subchn->proc_entry1 = entry;
		}
	}
}

/*
 *  Timer functions
 */

static unsigned long snd_pcm1_timer_resolution(snd_timer_t * timer)
{
	snd_pcm_subchn_t * subchn;
	
	subchn = (snd_pcm_subchn_t *)timer->private_data;
	return subchn->timer_resolution;
}

static void snd_pcm1_timer_start(snd_timer_t * timer)
{
	unsigned long flags;
	snd_pcm_subchn_t * subchn;
	
	subchn = (snd_pcm_subchn_t *)timer->private_data;
	spin_lock_irqsave(&subchn->timer_lock, flags);
	subchn->timer_running = 1;
	spin_unlock_irqrestore(&subchn->timer_lock, flags);
}

static void snd_pcm1_timer_stop(snd_timer_t * timer)
{
	unsigned long flags;
	snd_pcm_subchn_t * subchn;
	
	subchn = (snd_pcm_subchn_t *)timer->private_data;
	spin_lock_irqsave(&subchn->timer_lock, flags);
	subchn->timer_running = 0;
	spin_unlock_irqrestore(&subchn->timer_lock, flags);
}

static struct snd_stru_timer_hardware snd_pcm1_timer =
{
	SND_TIMER_HW_AUTO | SND_TIMER_HW_SLAVE,	/* flags */
	0,			/* resolution in us */
	1,			/* high ticks */
	NULL,			/* open */
	NULL,			/* close */
	snd_pcm1_timer_resolution, /* resolution */
	snd_pcm1_timer_start,	/* start */
	snd_pcm1_timer_stop,	/* stop */
};

/*
 *  Register functions
 */

static snd_minor_t snd_pcm1_reg =
{
	"digital audio",	/* comment */
	NULL,			/* reserved */

	NULL,			/* unregister */

	NULL,			/* lseek */
	snd_pcm1_read,		/* read */
	snd_pcm1_write,		/* write */
	snd_pcm1_open,		/* open */
	snd_pcm1_release,	/* release */
	snd_pcm1_poll,		/* poll */
	snd_pcm1_ioctl,		/* ioctl */
	NULL,			/* mmap */
};

static void snd_pcm1_channel_free(void *private_data)
{
	snd_pcm1_channel_t *pchn1 = (snd_pcm1_channel_t *)private_data;
#ifdef CONFIG_SND_OSSEMUL
	struct snd_stru_pcm1_oss_setup *setup, *setupn;
#endif

	if (pchn1) {
#ifdef CONFIG_SND_OSSEMUL
		for (setup = pchn1->setup_list; setup; setup = setupn) {
			setupn = setup->next;
			snd_kfree(setup->task_name);
			snd_kfree(setup);
		}
#endif
		snd_kfree(pchn1);
	}
}

snd_pcm_t *snd_pcm1_new_device(snd_card_t * card, char *id,
			       int playback_count, int capture_count)
{
	snd_pcm_t *pcm;
	snd_pcm1_channel_t *playback, *capture;

	if (!card)
		return NULL;
	pcm = snd_pcm_new_device(card, id, &snd_pcm1_reg, playback_count, capture_count);
	if (!pcm)
		return NULL;
	pcm->type = SND_PCM_TYPE_1;
	playback = (snd_pcm1_channel_t *) snd_kcalloc(sizeof(*playback), GFP_KERNEL);
	if (playback == NULL) {
		snd_pcm_free(pcm);
		return NULL;
	}
#ifdef CONFIG_SND_OSSEMUL
	init_MUTEX(&playback->setup_mutex);
#endif
	pcm->playback.private_data = playback;
	pcm->playback.private_free = snd_pcm1_channel_free;
	capture = (snd_pcm1_channel_t *) snd_kcalloc(sizeof(*capture), GFP_KERNEL);
	if (capture == NULL) {
		snd_pcm_free(pcm);
		return NULL;
	}
#ifdef CONFIG_SND_OSSEMUL
	init_MUTEX(&capture->setup_mutex);
#endif
	pcm->capture.private_data = capture;
	pcm->capture.private_free = snd_pcm1_channel_free;
	return pcm;
}

static void snd_pcm1_timer_init(snd_pcm_subchn_t *subchn)
{
	int device;
	snd_timer_t *timer;
	
	timer = snd_timer_new_device(subchn->pcm->card, "PCM");
	if (timer == NULL)
		return;
	device = subchn->pcm->device;
	device <<= SND_TIMER_PCM_DEV_SHIFT;
	device |= (subchn->number << 1) | (subchn->capture ? 1 : 0);
	sprintf(timer->name, "PCM %s %i-%i-%i",
			subchn->capture ? "capture" : "playback",
			SND_TIMER_PCM_CARD(device),
			SND_TIMER_PCM_DEV(device),
			SND_TIMER_PCM_SUBDEV(device));
	memcpy(&timer->hw, &snd_pcm1_timer, sizeof(timer->hw));
	if (snd_timer_register(timer, SND_TIMER_DEV_FLG_PCM | device) < 0)
		snd_timer_free(timer);
	timer->private_data = subchn;
	subchn->timer = timer;
}

static int snd_pcm1_register_notify(unsigned short minor, snd_pcm_t * pcm)
{
	snd_pcm_subchn_t *subchn;

	if (pcm->type != SND_PCM_TYPE_1)
		return -EINVAL;
	snd_pcm1_proc_init(pcm);
	for (subchn = pcm->playback.subchn; subchn; subchn = subchn->next)
		snd_pcm1_timer_init(subchn);
	for (subchn = pcm->capture.subchn; subchn; subchn = subchn->next)
		snd_pcm1_timer_init(subchn);
	return 0;
}

static int snd_pcm1_unregister_notify(unsigned short minor, snd_pcm_t * pcm)
{
	snd_pcm_subchn_t *subchn;

	if (pcm->type != SND_PCM_TYPE_1)
		return -EINVAL;
	for (subchn = pcm->playback.subchn; subchn; subchn = subchn->next) {
		snd_timer_unregister(subchn->timer);
		subchn->timer = NULL;
	}
	for (subchn = pcm->capture.subchn; subchn; subchn = subchn->next) {
		snd_timer_unregister(subchn->timer);
		subchn->timer = NULL;
	}
	return 0;
}

/*
 *  ENTRY functions
 */

static struct snd_stru_pcm_notify snd_pcm1_notify =
{
	snd_pcm1_register_notify,
	snd_pcm1_unregister_notify,
	NULL
};

int init_module(void)
{
	snd_control_register_ioctl(snd_pcm1_control_ioctl);
	snd_pcm_notify(&snd_pcm1_notify, 0);
	return 0;
}

void cleanup_module(void)
{
	snd_pcm_notify(&snd_pcm1_notify, 1);
	snd_control_unregister_ioctl(snd_pcm1_control_ioctl);
}

EXPORT_SYMBOL(snd_pcm1_dma_alloc);
EXPORT_SYMBOL(snd_pcm1_dma_free);
EXPORT_SYMBOL(snd_pcm1_playback_dma);
EXPORT_SYMBOL(snd_pcm1_playback_dma_ulaw);
EXPORT_SYMBOL(snd_pcm1_playback_dma_ulaw_loud);
EXPORT_SYMBOL(snd_pcm1_playback_dma_neutral);
EXPORT_SYMBOL(snd_pcm1_capture_dma);
EXPORT_SYMBOL(snd_pcm1_capture_dma_ulaw);
EXPORT_SYMBOL(snd_pcm1_capture_dma_ulaw_loud);
EXPORT_SYMBOL(snd_pcm1_dma_move);
EXPORT_SYMBOL(snd_pcm1_clear_subchannel);
EXPORT_SYMBOL(snd_pcm1_fill_with_neutral);
EXPORT_SYMBOL(snd_pcm1_new_device);
EXPORT_SYMBOL(snd_pcm1_proc_format);
EXPORT_SYMBOL(snd_pcm1_subchannel_free);
