/*
 *  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 "driver.h"
#include "minors.h"
#include "control.h"
#include "info.h"
#include "pcm1.h"
#include "ulaw.h"

#if 0
#define SND_PCM1_DEBUG_BUFFERS
#endif

static unsigned int snd_pcm1_align1(snd_pcm1_channel_t *pchn, unsigned int value);
static int snd_pcm1_playback_pause(snd_pcm1_t * pcm1, int enable);

/*

 */

int snd_pcm1_dma_alloc(snd_pcm1_t * pcm1, int direction, snd_dma_t * dma, char *ident)
{
	int err;
	snd_pcm1_channel_t *pchn;

	if ((err = snd_dma_malloc(pcm1->card, dma, ident, 0)) < 0)
		return err;
	pchn = direction == SND_PCM1_PLAYBACK ?
					&pcm1->playback : &pcm1->record;
	pchn->size = dma->size;
	pchn->buffer = dma->buf;
	pchn->dmaptr = dma;
	pchn->frag_size = 0;
	if (pchn->size < 4096) {
		snd_printd("Invalid audio DMA size - %i\n", pchn->size);
		snd_dma_free(pcm1->card, dma, 0);
		return -ENOMEM;
	}
	pchn->flags |= SND_PCM1_FLG_DMAOK;
	return 0;
}

int snd_pcm1_dma_free(snd_pcm1_t * pcm1, int direction, snd_dma_t * dma)
{
	snd_pcm1_channel_t *pchn;

	snd_dma_free(pcm1->card, dma, 0);
	pchn = direction == SND_PCM1_PLAYBACK ?
					&pcm1->playback : &pcm1->record;
	pchn->flags &= ~SND_PCM1_FLG_DMAOK;
	return 0;
}

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

void snd_pcm1_fill_with_neutral(snd_pcm1_t * pcm1, snd_pcm1_channel_t * pchn)
{
	unsigned int size;

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

static unsigned int snd_pcm1_bytes_per_second(snd_pcm1_channel_t * pchn)
{
	unsigned int bps;

	bps = pchn->real_rate * pchn->voices;
	if (pchn->mode & SND_PCM1_MODE_16) {
		bps <<= 1;
	} else if (pchn->mode & SND_PCM1_MODE_24) {
		bps *= 3;
	} else if (pchn->mode & (SND_PCM1_MODE_32|SND_PCM1_MODE_FLOAT)) {
		bps <<= 2;
	} else if (pchn->mode & SND_PCM1_MODE_FLOAT64) {
		bps <<= 3;
	} else if (pchn->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_pcm1_channel_t *pchn)
{
	unsigned int bps, sec, bsize;
	
	/* this is not very good algorithm */
	sec = 1000000000;
	bps = snd_pcm1_bytes_per_second(pchn);
	bsize = pchn->block_size;
	if (!bps) {
		pchn->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;
	}
	pchn->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
}

/*

 */

unsigned short snd_pcm1_file_flags(struct file *file)
{
#ifdef LINUX_2_1
	switch (file->f_mode & (FMODE_WRITE | FMODE_READ)) {
	case FMODE_WRITE:
		return SND_PCM1_LFLG_PLAY;
	case FMODE_READ:
		return SND_PCM1_LFLG_RECORD;
	default:
		return SND_PCM1_LFLG_BOTH;
	}
#else
	switch (file->f_flags & O_ACCMODE) {
	case O_WRONLY:
		return SND_PCM1_LFLG_PLAY;
	case O_RDONLY:
		return SND_PCM1_LFLG_RECORD;
	default:
		return SND_PCM1_LFLG_BOTH;
	}
#endif
}

/*
 *  some shared routines for lowlevel driver
 */

void snd_pcm1_playback_dma(snd_pcm1_t * pcm1,
			   unsigned char *buffer, unsigned int offset,
			   unsigned char *user, unsigned int count)
{
	copy_from_user(&buffer[offset], user, count);
}

void snd_pcm1_playback_dma_ulaw(snd_pcm1_t * pcm1,
			      unsigned char *buffer, unsigned int offset,
				unsigned char *user, unsigned int count)
{
	if (pcm1->playback.mode & SND_PCM1_MODE_ULAW)
		snd_translate_from_user(snd_ulaw_dsp, &buffer[offset],
				        user, count);
	else
		copy_from_user(&buffer[offset], user, count);
}

void snd_pcm1_playback_dma_ulaw_loud(snd_pcm1_t * pcm1,
			      	     unsigned char *buffer, unsigned int offset,
				     unsigned char *user, unsigned int count)
{
	if (pcm1->playback.mode & SND_PCM1_MODE_ULAW)
		snd_translate_from_user(snd_ulaw_dsp_loud, &buffer[offset],
				        user, count);
	else
		copy_from_user(&buffer[offset], user, count);
}

void snd_pcm1_playback_dma_neutral(snd_pcm1_t * pcm1,
			           unsigned char *buffer, unsigned int offset,
				   unsigned int count,
				   unsigned char neutral_byte)
{
	memset(&buffer[offset], neutral_byte, count);
}

void snd_pcm1_record_dma(snd_pcm1_t * pcm1,
			 unsigned char *buffer, unsigned int offset,
			 unsigned char *user, unsigned int count)
{
	copy_to_user(user, &buffer[offset], count);
}

void snd_pcm1_record_dma_ulaw(snd_pcm1_t * pcm1,
			      unsigned char *buffer, unsigned int offset,
			      unsigned char *user, unsigned int count)
{
	if (pcm1->record.mode & SND_PCM1_MODE_ULAW)
		snd_translate_to_user(snd_dsp_ulaw, user,
				      &buffer[offset], count);
	else
		copy_to_user(user, &buffer[offset], count);
}

void snd_pcm1_record_dma_ulaw_loud(snd_pcm1_t * pcm1,
			      	   unsigned char *buffer, unsigned int offset,
				   unsigned char *user, unsigned int count)
{
	if (pcm1->record.mode & SND_PCM1_MODE_ULAW)
		snd_translate_to_user(snd_dsp_ulaw_loud, user,
				      &buffer[offset], count);
	else
		copy_to_user(user, &buffer[offset], count);
}

void snd_pcm1_dma_move(snd_pcm1_t * pcm1,
		       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);
}

/*
 *  interrupt callbacks from lowlevel driver
 */

static inline int snd_pcm1_playback_ok(snd_pcm1_channel_t * pchn)
{
	unsigned long flags;
	int result, freeblks;

	snd_spin_lock(pchn, lock, &flags);
	freeblks = pchn->blocks - pchn->used;
	result = pchn->used <= pchn->blocks_max &&
	         freeblks >= pchn->blocks_room;
	snd_spin_unlock(pchn, lock, &flags);
	return result;
}

static inline int snd_pcm1_record_ok(snd_pcm1_channel_t * pchn)
{
	unsigned long flags;
	int result;

	snd_spin_lock(pchn, lock, &flags);
	if ((pchn->flags & SND_PCM1_FLG_NEEDEMPTY) && pchn->used > 0) {
		result = 1;
	} else {
		result = pchn->used >= pchn->blocks_min;
	}
	snd_spin_unlock(pchn, lock, &flags);
	return result;
}

static void snd_pcm1_interrupt_playback(snd_pcm1_t * pcm1)
{
	unsigned long flags;
	snd_pcm1_channel_t *pchn;
	int trigger = 0;

	pchn = &pcm1->playback;
	if (!(pchn->flags & SND_PCM1_FLG_TRIGGER)) {
		snd_printd("snd_pcm_interrupt_playback: Oops, playback interrupt when playback isn't active!!!\n");
		return;
	}
	if (pchn->used <= 0) {
		snd_printd("snd_pcm_interrupt_playback: Internal error (1)\n");
		return;
	}
	if (pchn->flags & SND_PCM1_FLG_PAUSE) {
		pchn->hw.trigger(pcm1, 0);
		return;
	}
	if (pchn->hw.flags & SND_PCM1_HW_AUTODMA) {
		/* try clear first few bytes from non-active block */
		pchn->hw.dma_neutral(pcm1, pchn->buffer,
				     pchn->tail * pchn->block_size,
				     16, pchn->neutral_byte);
	}
	snd_spin_lock(&pcm1->playback, lock, &flags);
	if (pchn->used == 1) {
		if (!(pchn->flags & SND_PCM1_FLG_SYNC)) {
			pchn->total_xruns++;
			pchn->xruns++;
		}
	}
	pchn->interrupts++;
	if (pchn->used > 0) {
		pchn->used--;
		pchn->processed_bytes += pchn->block_size;
	}
	pchn->tail++;
	pchn->tail %= pchn->blocks;
	if (pchn->used > 0) {
		snd_pcm1_lock(pchn, pchn->tail);
		trigger = 1;
	} else {
		snd_pcm1_lockzero(pchn);
	}
	snd_spin_unlock(&pcm1->playback, lock, &flags);
	if (trigger) {
		if (!(pchn->hw.flags & SND_PCM1_HW_AUTODMA)) {
			pchn->hw.prepare(pcm1,
					 pchn->buffer, pchn->used_size,
					 pchn->tail * pchn->block_size,
					 pchn->block_size);
			pchn->hw.trigger(pcm1, 1);
		}
	} else {
		if (pchn->hw.flags & SND_PCM1_HW_AUTODMA)
			pchn->hw.trigger(pcm1, 0);
		snd_pcm1_clear_time(pchn);
		pchn->flags &= ~SND_PCM1_FLG_TRIGGER;
	}
	if ((snd_getlock(pchn, sleep) & SND_WK_SLEEP) && snd_pcm1_playback_ok(pchn)) {
		snd_wakeup(pchn, sleep);
		snd_getlock(pchn, sleep) &= ~SND_WK_SLEEP;
	}
	if (pchn->timer && (pchn->flags & SND_PCM1_FLG_TIMER)) {
		snd_timer_interrupt(pchn->timer, 1);
	}
}

static void snd_pcm1_interrupt_record(snd_pcm1_t * pcm1)
{
	unsigned long flags;
	snd_pcm1_channel_t *pchn;
	int trigger;

	pchn = &pcm1->record;
	if (!(pchn->flags & SND_PCM1_FLG_TRIGGER)) {
		snd_printd("snd_pcm_interrupt_record: Oops, record interrupt when record isn't active!!!\n");
		return;
	}
	trigger = 0;
	snd_spin_lock(pchn, lock, &flags);
	if (!(pchn->flags & SND_PCM1_FLG_SYNC))
		trigger = 1;
	if (pchn->used < pchn->blocks) {
		pchn->used++;
	} else {
		pchn->total_xruns++;
		pchn->xruns++;
		trigger = 0;
	}
	pchn->interrupts++;
	pchn->processed_bytes += pchn->block_size;
	pchn->head++;
	pchn->head %= pchn->blocks;
	snd_pcm1_lock(pchn, pchn->head);
	snd_spin_unlock(pchn, lock, &flags);
	if (trigger) {
		if (!(pchn->hw.flags & SND_PCM1_HW_AUTODMA)) {
			pchn->hw.prepare(pcm1,
					 pchn->buffer, pchn->used_size,
					 pchn->head * pchn->block_size,
					 pchn->block_size);
			pchn->hw.trigger(pcm1, 1);
		}
	} else {
		if (pchn->hw.flags & SND_PCM1_HW_AUTODMA)
			pchn->hw.trigger(pcm1, 0);
		snd_pcm1_clear_time(pchn);
		pchn->flags &= ~SND_PCM1_FLG_TRIGGER;
	}
	if ((snd_getlock(pchn, sleep) & SND_WK_SLEEP) && snd_pcm1_record_ok(pchn)) {
		snd_wakeup(pchn, sleep);
		snd_getlock(pchn, sleep) &= ~SND_WK_SLEEP;
	}
	if (pchn->timer && (pchn->flags & SND_PCM1_FLG_TIMER)) {
		snd_timer_interrupt(pchn->timer, 1);
	}
}

/*
 *  trigger standard buffered playback
 */

static void snd_pcm1_trigger_playback(snd_pcm1_t * pcm1, snd_pcm1_channel_t * pchn)
{
	unsigned long flags;

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

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

static void snd_pcm1_playback_underrun(snd_pcm1_t *pcm1,
				       snd_pcm1_channel_t *pchn)
{
	if (pchn->frag_size > 0)
		pchn->hw.dma_move(pcm1, pchn->buffer, 0,
			          pchn->buffer,
			          pchn->head * pchn->block_size,
			          pchn->frag_size);
	pchn->head = pchn->tail = 0;
	pchn->lastxruns = pchn->xruns;
}

/*
 *  user to dma
 */

static int snd_pcm1_user_to_buffer(struct file *file, snd_pcm1_t * pcm1,
				   const char *buf, int count)
{
	unsigned long flags;
	snd_pcm1_channel_t *pchn;
	int result, tmp;

	if (count <= 0 || !buf)
		return 0;
	if (verify_area(VERIFY_READ, buf, count))
		return -EFAULT;

	pchn = &pcm1->playback;

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

	result = 0;

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

		copy_from_user(buffer, buf, 8);
		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(pchn)) {
			if (!(pchn->flags & SND_PCM1_FLG_TRIGGER))
				snd_pcm1_trigger_playback(pcm1, pchn);
			if (file->f_flags & O_NONBLOCK)
				return result;
			if (pchn->flags & SND_PCM1_FLG_PAUSE)
				return result;
			if (!(pchn->flags & SND_PCM1_FLG_ENABLE))
				return result;
			snd_spin_lock(pchn, sleep_lock, &flags);
			snd_getlock(pchn, sleep) |= SND_WK_SLEEP;
			snd_sleep(pchn, sleep, 10 * HZ);
			snd_getlock(pchn, sleep) &= ~SND_WK_SLEEP;
			snd_spin_unlock(pchn, sleep_lock, &flags);
			if (snd_sleep_abort(pchn, sleep)) {
				return !result ? -EINTR : result;
			}
			if (pchn->used >= pchn->blocks && snd_timeout(pchn, sleep)) {
				snd_printd("pcm_user_to_dma: timeout, new block discarded\n");
				return -EIO;
			}
		}

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

		snd_spin_lock(pchn, lock, &flags);
		tmp = pchn->block_size - pchn->frag_size;
		if (tmp > count)
			tmp = count;	/* correction */
		if (pchn->lastxruns != pchn->xruns)
			snd_pcm1_playback_underrun(pcm1, pchn);
		snd_spin_unlock(pchn, lock, &flags);

		pchn->hw.dma(pcm1,
			     pchn->buffer,
		       	     (pchn->head * pchn->block_size) + pchn->frag_size,
			     (char *) buf, tmp);
		snd_pcm_proc_write(&pcm1->pcm->playback, buf, tmp);

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

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

/*
 *  trigger standard buffered record
 */

static void snd_pcm1_trigger_record(snd_pcm1_t * pcm1, snd_pcm1_channel_t * pchn)
{
	unsigned long flags;

	if (pchn->used >= pchn->blocks)
		return;
	snd_spin_lock(pchn, lock, &flags);
	pchn->flags &= ~SND_PCM1_FLG_NEEDEMPTY;
	if (!(pchn->flags & SND_PCM1_FLG_TRIGGER)) {
		pchn->flags |= SND_PCM1_FLG_TRIGGERA;
		snd_pcm1_lock(pchn, pchn->head);
		snd_spin_unlock(pchn, lock, &flags);
		pchn->lastxruns = pchn->xruns;
		if (!pchn->used)
			pchn->head = pchn->tail = 0;
		if (pchn->head != 0)
			snd_printd( "Oops, record start when head = %i\n", pchn->head);
		pchn->hw.prepare(pcm1,
				 pchn->buffer, pchn->used_size,
				 pchn->head * pchn->block_size,
				 pchn->block_size);
		pchn->hw.trigger(pcm1, 1);
		if (pchn->flags & SND_PCM1_FLG_TIME) {
			do_gettimeofday(&pchn->time);
		}
	} else {
		snd_spin_unlock(pchn, lock, &flags);
	}
}

/*
 *  dma to user
 */

static int snd_pcm1_buffer_to_user(struct file *file, snd_pcm1_t * pcm1,
				   char *buf, int count)
{
	unsigned long flags;
	snd_pcm1_channel_t *pchn;
	int result, tmp;

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

	pchn = &pcm1->record;

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

	if (pchn->flags & SND_PCM1_FLG_NEUTRAL)
		snd_pcm1_fill_with_neutral(pcm1, pchn);

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

	while (count > 0) {
		while (!snd_pcm1_record_ok(pchn)) {
			snd_pcm1_trigger_record(pcm1, pchn);
			if (file->f_flags & O_NONBLOCK)
				return result;
			snd_spin_lock(pchn, sleep_lock, &flags);
			snd_getlock(pchn, sleep) |= SND_WK_SLEEP;
			snd_sleep(pchn, sleep, 10 * HZ);
			snd_getlock(pchn, sleep) &= ~SND_WK_SLEEP;
			snd_spin_unlock(pchn, sleep_lock, &flags);
			if (snd_sleep_abort(pchn, sleep)) {
				return !result ? -EINTR : result;
			}
			if (!pchn->used && snd_timeout(pchn, sleep)) {
				snd_printd("snd_pcm_dma_to_user: data timeout\n");
				return -EIO;
			}
		}

		snd_spin_lock(pchn, lock, &flags);
		tmp = count <= pchn->frag_size ? count : pchn->frag_size;
		snd_spin_unlock(pchn, lock, &flags);
		pchn->hw.dma(pcm1,
			     pchn->buffer,
			     (pchn->tail * pchn->block_size) +
			         (pchn->block_size - pchn->frag_size),
			     buf, tmp);
		if (!verify_area(VERIFY_READ, buf, tmp))
			snd_pcm_proc_write(&pcm1->pcm->record, buf, tmp);

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

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

	return result;
}

/*
 *  synchronize playback
 */

static int snd_pcm1_drain_playback(snd_pcm1_t * pcm1)
{
	unsigned long flags;
	snd_pcm1_channel_t *pchn;

#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("drain playback!!!\n");
#endif
	pchn = &pcm1->playback;
	snd_spin_lock(pchn, lock, &flags);
	if ((pchn->flags & SND_PCM1_FLG_TRIGGER) ||
	    (pchn->flags & SND_PCM1_FLG_PAUSE)) {
		pchn->hw.trigger(pcm1, 0);
		snd_pcm1_clear_time(pchn);
		pchn->flags &= ~SND_PCM1_FLG_TRIGGER;
	}
	snd_spin_unlock(pchn, lock, &flags);
	pchn->flags |= SND_PCM1_FLG_NEUTRAL;
	snd_pcm1_fill_with_neutral(pcm1, pchn);
	snd_spin_lock(pchn, lock, &flags);
	pchn->flags &= ~SND_PCM1_FLG_TRIGGER1;
	pchn->head = pchn->tail = pchn->used = 0;
	pchn->frag_size = pchn->processed_bytes = 0;
	snd_pcm1_lockzero(pchn);
	snd_spin_unlock(pchn, lock, &flags);
	return 0;
}

static int snd_pcm1_flush_playback(snd_pcm1_t * pcm1, int close_flag)
{
	int err = 0;
	unsigned long flags;
	snd_pcm1_channel_t *pchn;

#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("flush playback!!!\n");
#endif
	pchn = &pcm1->playback;
	if (pchn->flags & SND_PCM1_FLG_PAUSE)
		snd_pcm1_playback_pause(pcm1, 0);
	snd_spin_lock(pchn, lock, &flags);
	pchn->flags |= SND_PCM1_FLG_SYNC;
	if (pchn->used < pchn->blocks && pchn->frag_size > 0) {

		unsigned int size, count, offset;

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

	while (pchn->used) {
		snd_spin_lock(pchn, sleep_lock, &flags);
		snd_getlock(pchn, sleep) |= SND_WK_SLEEP;
		snd_sleep(pchn, sleep, 10 * HZ);
		snd_getlock(pchn, sleep) &= ~SND_WK_SLEEP;
		snd_spin_unlock(pchn, sleep_lock, &flags);
		if (snd_sleep_abort(pchn, sleep)) {
			err = -EINTR;
			break;
		}
		if (pchn->used && snd_timeout(pchn, sleep)) {
			snd_printd("snd_pcm1_flush_playback: timeout, skipping waiting blocks\n");
			err = -EAGAIN;
			break;
		}
	}
	pchn->flags &= ~SND_PCM1_FLG_SYNC;
	if (close_flag || (err != -EINTR && err != -EAGAIN))
		snd_pcm1_drain_playback(pcm1);
	return err;
}

static int snd_pcm1_drain_record(snd_pcm1_t * pcm1)
{
	unsigned long flags;
	snd_pcm1_channel_t *pchn;

#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("drain record!!!\n");
#endif
	pchn = &pcm1->record;
	snd_spin_lock(pchn, lock, &flags);
	if (pchn->flags & SND_PCM1_FLG_TRIGGER) {
		pchn->hw.trigger(pcm1, 0);
		snd_pcm1_clear_time(pchn);
		pchn->flags &= ~SND_PCM1_FLG_TRIGGER;
	}
	snd_spin_unlock(pchn, lock, &flags);
	pchn->flags |= SND_PCM1_FLG_NEUTRAL;
	snd_pcm1_fill_with_neutral(pcm1, pchn);
	snd_spin_lock(pchn, lock, &flags);
	pchn->flags &= ~SND_PCM1_FLG_TRIGGER1;
	pchn->head = pchn->tail = pchn->used = 0;
	pchn->frag_size = pchn->block_size;
	pchn->processed_bytes = 0;
	snd_pcm1_lockzero(pchn);
	snd_spin_unlock(pchn, lock, &flags);
	return 0;
}

static int snd_pcm1_flush_record(snd_pcm1_t * pcm1)
{
	int err = 0;
	unsigned long flags;
	snd_pcm1_channel_t *pchn;

#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("flush record!!!\n");
#endif
	pchn = &pcm1->record;
	pchn->flags |= SND_PCM1_FLG_SYNC;
	while (pchn->flags & SND_PCM1_FLG_TRIGGER) {	/* still recording? */
		snd_spin_lock(pchn, sleep_lock, &flags);
		snd_getlock(pchn, sleep) |= SND_WK_SLEEP;
		snd_sleep(pchn, sleep, 10 * HZ);
		snd_getlock(pchn, sleep) &= ~SND_WK_SLEEP;
		snd_spin_unlock(pchn, sleep_lock, &flags);
		if (snd_sleep_abort(pchn, sleep)) {
			err = -EINTR;
			break;
		}
		if ((pchn->flags & SND_PCM1_FLG_TRIGGER) && snd_timeout(pchn, sleep)) {
			snd_printd("snd_pcm1_flush_record: sync timeout\n");
			err = -EAGAIN;
			break;
		}
	}
	pchn->flags &= ~SND_PCM1_FLG_SYNC;
	snd_pcm1_drain_record(pcm1);
	return 0;
}

/*
 *  other things
 */

static unsigned int snd_pcm1_align1(snd_pcm1_channel_t *pchn, unsigned int value)
{
	unsigned int tmp;

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

static unsigned int snd_pcm1_align(snd_pcm1_t * pcm1,
                                   int direction, unsigned int value)
{
	unsigned int tmp;
	snd_pcm1_channel_t *pchn;

	pchn = direction == SND_PCM1_PLAYBACK ? &pcm1->playback : &pcm1->record;
	if (pchn->mode & SND_PCM1_MODE_16) {
		tmp = 2;
	} else if (pchn->mode & SND_PCM1_MODE_24) {
		tmp = (pchn->voices * 3) - 1;
		value /= tmp;
		value *= tmp;
		if (value < pchn->hw.align)
			value = ((pchn->hw.align + 2) / 3) * 3;
		if (!value)
			value = pchn->voices * 3;
		return value;
	} else if (pchn->mode & (SND_PCM1_MODE_32|SND_PCM1_MODE_FLOAT)) {
		tmp = 4;
	} else if (pchn->mode & SND_PCM1_MODE_FLOAT64) {
		tmp = 4;
	} else {
		tmp = 1;
	}
	value &= ~((pchn->voices * tmp) - 1);
	value &= ~pchn->hw.align;
	if (!value)
		value = pchn->hw.align + 1;
	pchn->hw.ioctl(pcm1, SND_PCM1_IOCTL_FRAG, (unsigned long *)&value);
	return value;
}

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

	pcm = pcm1->pcm;
	info = xinfo;
	if (verify_area(VERIFY_WRITE, info, sizeof(snd_pcm_info_t)))
		return -EFAULT;
	info = &ginfo;
	memset(&ginfo, 0, sizeof(ginfo));
	ginfo.type = pcm1->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);
	copy_to_user(xinfo, &ginfo, sizeof(ginfo));
	return 0;
}

static int snd_pcm1_playback_info(snd_pcm1_t * pcm1,
				  snd_pcm_playback_info_t * xinfo)
{
	snd_pcm_playback_info_t ginfo, *info;
	snd_pcm1_channel_t *pchn;
	int i;

	info = xinfo;
	if (verify_area(VERIFY_WRITE, xinfo, sizeof(snd_pcm_playback_info_t)))
		return -EFAULT;
	info = &ginfo;
	memset(&ginfo, 0, sizeof(ginfo));
	pchn = &pcm1->playback;
	ginfo.flags = pchn->hw.flags & SND_PCM1_HW_COPYMASK;
	ginfo.formats = pchn->hw.formats;
	ginfo.min_rate = pchn->hw.min_rate;
	ginfo.max_rate = pchn->hw.max_rate;
	ginfo.min_channels = 1;
	ginfo.max_channels = pchn->hw.max_voices;
	ginfo.buffer_size = pchn->size;
	if (pchn->hw.flags & SND_PCM1_HW_8BITONLY) {
		if (pchn->mode & SND_PCM1_MODE_16)
			ginfo.buffer_size <<= 1;
	}
	if (pchn->hw.flags & SND_PCM1_HW_16BITONLY) {
		if (!(pchn->mode & SND_PCM1_MODE_16))
			ginfo.buffer_size >>= 1;
	}
	ginfo.min_fragment_size = 1;
	for (i = 1; i < pchn->hw.min_fragment; i++)
		ginfo.min_fragment_size <<= 1;
	ginfo.max_fragment_size = ginfo.buffer_size / 2;
	ginfo.fragment_align = pchn->hw.align;
	ginfo.hw_formats = pchn->hw.hw_formats;
	ginfo.switches = snd_switch_count(&pcm1->pcm->playback.switches);
	copy_to_user(xinfo, &ginfo, sizeof(ginfo));
	return 0;
}

static int snd_pcm1_record_info(snd_pcm1_t * pcm1,
			        snd_pcm_record_info_t * xinfo)
{
	snd_pcm_record_info_t ginfo, *info;
	snd_pcm1_channel_t *pchn;
	int i;

	info = xinfo;
	if (verify_area(VERIFY_WRITE, xinfo, sizeof(snd_pcm_record_info_t)))
		return -EFAULT;
	info = &ginfo;
	memset(&ginfo, 0, sizeof(ginfo));
	pchn = &pcm1->record;
	ginfo.flags = pchn->hw.flags & SND_PCM1_HW_COPYMASK;
	ginfo.formats = pchn->hw.formats;
	ginfo.min_rate = pchn->hw.min_rate;
	ginfo.max_rate = pchn->hw.max_rate;
	ginfo.min_channels = 1;
	ginfo.max_channels = pchn->hw.max_voices;
	ginfo.buffer_size = pchn->size;
	if (pchn->hw.flags & SND_PCM1_HW_8BITONLY) {
		if (pchn->mode & SND_PCM1_MODE_16)
			ginfo.buffer_size <<= 1;
	}
	if (pchn->hw.flags & SND_PCM1_HW_16BITONLY) {
		if (!(pchn->mode & SND_PCM1_MODE_16))
			ginfo.buffer_size >>= 1;
	}
	ginfo.min_fragment_size = 1;
	for (i = 1; i < pchn->hw.min_fragment; i++)
		ginfo.min_fragment_size <<= 1;
	ginfo.max_fragment_size = ginfo.buffer_size / 2;
	ginfo.fragment_align = pchn->hw.align;
	ginfo.hw_formats = pchn->hw.hw_formats;
	ginfo.switches = snd_switch_count(&pcm1->pcm->record.switches);
	copy_to_user(xinfo, &ginfo, sizeof(ginfo));
	return 0;
}

static int snd_pcm1_format(snd_pcm1_t * pcm1, int direction,
			   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_channel_t *pchn;

	if (verify_area(VERIFY_READ, _format, sizeof(format)))
		return -EFAULT;
	copy_from_user(&format, _format, sizeof(format));
	pchn = direction == SND_PCM1_PLAYBACK ?
					&pcm1->playback : &pcm1->record;
	if (format.rate < pchn->hw.min_rate)
		format.rate = pchn->hw.min_rate;
	if (format.rate > pchn->hw.max_rate)
		format.rate = pchn->hw.max_rate;
	if (format.channels < 1)
		return -EINVAL;
	if (format.channels > pchn->hw.max_voices)
		return -EINVAL;
	if (!(pchn->hw.formats & (1 << format.format))) {
		if (open_flag) {
			/* try find first useable format */
			for (tmp = 0; tmp < 32; tmp++)
				if (pchn->hw.formats & (1 << tmp)) {
					format.format = tmp;
					break;
				}
			if (tmp >= 32)
				return -EINVAL;
		} else {
			return -EINVAL;
		}
	}
	err = direction == SND_PCM1_PLAYBACK ?
			snd_pcm1_flush_playback(pcm1, 0) :
			snd_pcm1_flush_record(pcm1);
	if (err < 0)
		return err;

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

static int snd_pcm1_playback_params(snd_pcm1_t * pcm1,
				    snd_pcm_playback_params_t * _params)
{
	snd_pcm_playback_params_t params;
	snd_pcm1_channel_t *pchn;
	int i, tmp;
	unsigned long flags;

	if (verify_area(VERIFY_READ, _params, sizeof(params)))
		return -EFAULT;
	copy_from_user(&params, _params, sizeof(params));
	if ((tmp = snd_pcm1_flush_playback(pcm1, 0)) < 0)
		return tmp;
	pchn = &pcm1->playback;
	snd_spin_lock(pchn, lock, &flags);
	for (tmp = i = 1; i < pchn->hw.min_fragment; i++)
		tmp <<= 1;
	if (params.fragment_size < tmp ||
	    params.fragment_size > pchn->size / 2) {
		snd_spin_unlock(pchn, lock, &flags);
		return -EINVAL;
	}
	pchn->used_size = pchn->size;
	if (pchn->hw.flags & SND_PCM1_HW_8BITONLY) {
		if (pchn->mode & SND_PCM1_MODE_16)
			pchn->used_size <<= 1;
	}
	if (pchn->hw.flags & SND_PCM1_HW_16BITONLY) {
		if (!(pchn->mode & SND_PCM1_MODE_16))
			pchn->used_size >>= 1;
	}
	pchn->block_size = snd_pcm1_align(pcm1, SND_PCM1_PLAYBACK,
	                                  params.fragment_size);
	pchn->blocks = pchn->used_size / pchn->block_size;
	pchn->used_size = pchn->blocks * pchn->block_size;
	if (params.fragments_max < 0) {
		pchn->blocks_max = pchn->blocks + params.fragments_max;
		if (pchn->blocks_max < 1)
			pchn->blocks_max = 1;
	} else {
		pchn->blocks_max = params.fragments_max;
		if (pchn->blocks_max > pchn->blocks - 1)
			pchn->blocks_max = pchn->blocks - 1;
	}
	pchn->blocks_room = params.fragments_room;
	if (pchn->blocks_room > pchn->blocks / 2)
		pchn->blocks_room = pchn->blocks / 2;
	snd_pcm1_new_timer_resolution(pchn);
	snd_spin_unlock(pchn, 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", pchn->used_size, pchn->blocks, pchn->block_size, pchn->blocks_max, pchn->blocks_room);
#endif
	return 0;
}

static int snd_pcm1_record_params(snd_pcm1_t * pcm1,
				  snd_pcm_record_params_t * _params)
{
	snd_pcm_record_params_t params;
	snd_pcm1_channel_t *pchn;
	int i, tmp;
	unsigned long flags;

	if (verify_area(VERIFY_READ, _params, sizeof(params)))
		return -EFAULT;
	copy_from_user(&params, _params, sizeof(params));
	if ((tmp = snd_pcm1_flush_record(pcm1)) < 0)
		return tmp;
	pchn = &pcm1->record;
	snd_spin_lock(pchn, lock, &flags);
	for (tmp = i = 1; i < pchn->hw.min_fragment; i++)
		tmp <<= 1;
	pchn->used_size = pchn->size;
	if (pchn->hw.flags & SND_PCM1_HW_8BITONLY) {
		if (pchn->mode & SND_PCM1_MODE_16)
			pchn->used_size <<= 1;
	}
	if (pchn->hw.flags & SND_PCM1_HW_16BITONLY) {
		if (!(pchn->mode & SND_PCM1_MODE_16))
			pchn->used_size >>= 1;
	}
	if (params.fragment_size < tmp ||
	    params.fragment_size > pchn->size / 2) {
		snd_spin_unlock(pchn, lock, &flags);
		return -EINVAL;
	}
	pchn->block_size = snd_pcm1_align(pcm1, SND_PCM1_RECORD,
					  params.fragment_size);
	pchn->frag_size = pchn->block_size;
	pchn->blocks = pchn->used_size / pchn->block_size;
	pchn->used_size = pchn->blocks * pchn->block_size;
	pchn->blocks_min = params.fragments_min;
	if (pchn->blocks_min > pchn->blocks / 2)
		pchn->blocks_min = pchn->blocks / 2;
	if (pchn->blocks_min < 1)
		pchn->blocks_min = 1;
	snd_pcm1_new_timer_resolution(pchn);
	snd_spin_unlock(pchn, lock, &flags);
#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("record_params: used_size = %i, blocks = %i, blocks_size = %i, blocks_min = %i\n", pchn->used_size, pchn->blocks, pchn->block_size, pchn->blocks_min);
#endif
	return 0;
}

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

	if (pchn->time.tv_sec == 0) {
		time->tv_sec = time->tv_usec = 0;
	} else {
		if ((bps = snd_pcm1_bytes_per_second(pchn)) == 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_pcm1_t * pcm1,
				    snd_pcm_playback_status_t * _status)
{
	snd_pcm_playback_status_t status;
	snd_pcm1_channel_t *pchn;
	unsigned long flags;
	int ptr;

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

static int snd_pcm1_record_status(snd_pcm1_t * pcm1,
				  snd_pcm_record_status_t * _status)
{
	snd_pcm_record_status_t status;
	snd_pcm1_channel_t *pchn;
	unsigned long flags;
	int ptr;

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

static int snd_pcm1_playback_pause1_enable(snd_pcm1_t * pcm1)
{
	unsigned int used, block_size, idx;
	unsigned char *buffer;
	
	pcm1->playback.flags &= ~SND_PCM1_FLG_TRIGGER;
	if (pcm1->playback.tail == 0) return 0;

	/* ok. create correct block order for auto-init DMA transfers */
	used = pcm1->playback.used;
	if (pcm1->playback.frag_size > 0)
		used++;
	block_size = pcm1->playback.block_size;
	if (pcm1->playback.tail + used >= pcm1->playback.blocks) {
		buffer = snd_malloc(pcm1->playback.size);
		if (!buffer) {
			snd_pcm1_drain_playback(pcm1);
			return -ENOMEM;
		}
		for (idx = 0; idx < used; idx++) {
			pcm1->playback.hw.dma_move(pcm1,
						   buffer,
						   idx * block_size,
						   pcm1->playback.buffer,
						   ((pcm1->playback.tail + idx) % pcm1->playback.blocks) * block_size,
						   block_size);
		}
		pcm1->playback.hw.dma_move(pcm1,
					   pcm1->playback.buffer,
					   0,
					   buffer,
					   0,
					   used * block_size);
		snd_free(buffer, pcm1->playback.size);
	} else {
		for (idx = 0; idx < used; idx++)
			pcm1->playback.hw.dma_move(pcm1,
						   pcm1->playback.buffer,
						   idx * block_size,
						   pcm1->playback.buffer,
						   ((pcm1->playback.tail + idx) % pcm1->playback.blocks) * block_size,
						   block_size);
	}
	pcm1->playback.head = pcm1->playback.used;
	pcm1->playback.tail = 0;
	return 0;
}

static int snd_pcm1_playback_pause(snd_pcm1_t * pcm1, int enable)
{
	int err = 0;
	unsigned long flags;
	snd_pcm1_channel_t *pchn;

	pchn = &pcm1->playback;
	snd_spin_lock(pchn, lock, &flags);
	if (enable) {
		if (pchn->flags & SND_PCM1_FLG_PAUSE) {
			snd_spin_unlock(pchn, lock, &flags);
			return 0;
		}
		pchn->flags |= SND_PCM1_FLG_PAUSE;
		if (pchn->hw.flags & SND_PCM1_HW_PAUSE) {
			err = pchn->hw.ioctl(pcm1,
					     SND_PCM1_IOCTL_PAUSE,
					     SND_PCM1_IOCTL_TRUE);
		} else {
			pcm1->playback.hw.trigger(pcm1, 0);
			snd_spin_unlock(pchn, lock, &flags);
			return snd_pcm1_playback_pause1_enable(pcm1);
		}
	} else {
		if (!(pcm1->playback.flags & SND_PCM1_FLG_PAUSE)) {
			snd_spin_unlock(pchn, lock, &flags);
			return 0;
		}
		pchn->flags &= ~SND_PCM1_FLG_PAUSE;
		if (pchn->hw.flags & SND_PCM1_HW_PAUSE) {
			err = pchn->hw.ioctl(pcm1,
					     SND_PCM1_IOCTL_PAUSE,
					     SND_PCM1_IOCTL_FALSE);
		} else {
			snd_spin_unlock(pchn, lock, &flags);
			snd_pcm1_trigger_playback(pcm1, pchn);
			return 0;
		}
	}
	snd_spin_unlock(pchn, lock, &flags);
	return err;
}

static int snd_pcm1_open_card(snd_pcm1_t * pcm1, struct file *file)
{
	mm_segment_t fs;
	unsigned short flags;
	int res;
	snd_pcm_t *pcm;
	snd_pcm_format_t format;
	snd_pcm_playback_params_t pparams;
	snd_pcm_record_params_t rparams;

	flags = snd_pcm1_file_flags(file);
#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("snd_pcm1_open_card (1) - pcm1 = 0x%lx, flags = %i, pcm->flags = 0x%x\n", (long) pcm1, flags, pcm1->flags);
#endif
	if (flags & pcm1->flags)
		return -EBUSY;	/* channel(s) already used */
	pcm = pcm1->pcm;
	if (flags & SND_PCM1_LFLG_PLAY)
		if (!(pcm->info_flags & SND_PCM_INFO_PLAYBACK))
			return -ENODEV;
	if (flags & SND_PCM1_LFLG_RECORD)
		if (!(pcm->info_flags & SND_PCM_INFO_RECORD))
			return -ENODEV;
	if (flags == SND_PCM1_LFLG_BOTH)
		if (!(pcm->info_flags & SND_PCM_INFO_DUPLEX))
			return -ENODEV;
	if (flags & SND_PCM1_LFLG_PLAY)
		snd_pcm1_clear_channel(&pcm1->playback);
	if (flags & SND_PCM1_LFLG_RECORD)
		snd_pcm1_clear_channel(&pcm1->record);
	memset(&format, 0, sizeof(format));
	format.format = SND_PCM_SFMT_MU_LAW;
	format.rate = SND_PCM1_DEFAULT_RATE;
	format.channels = 1;
	if (flags & SND_PCM1_LFLG_PLAY) {
		if ((res = pcm1->playback.hw.open(pcm1)) < 0)
			return res;
		pcm1->playback.flags |= SND_PCM1_FLG_ENABLE;
		pcm1->playback.ack = snd_pcm1_interrupt_playback;
		fs = snd_enter_user();
		if ((res = snd_pcm1_format(pcm1, SND_PCM1_PLAYBACK, &format, 1)) < 0) {
			snd_printk("Oops.. Playback format setup failed!!!\n");
			snd_leave_user(fs);
			pcm1->playback.hw.close(pcm1);
			return res;
		}
		memset(&pparams, 0, sizeof(pparams));
		pparams.fragment_size = 1024;
		pparams.fragments_max = -1;
		pparams.fragments_room = 1;
		if ((res = snd_pcm1_playback_params(pcm1, &pparams)) < 0) {
			snd_printk("Oops.. Playback parameters setup failed!!!\n");
			snd_leave_user(fs);
			pcm1->playback.hw.close(pcm1);
			return res;
		}
		snd_leave_user(fs);
	}
	if (flags & SND_PCM1_LFLG_RECORD) {
		if ((res = pcm1->record.hw.open(pcm1)) < 0) {
			if (flags & SND_PCM1_LFLG_PLAY) {
				pcm1->playback.hw.close(pcm1);
				pcm1->playback.flags &= ~SND_PCM1_FLG_ENABLE;
			}
			return res;
		}
		pcm1->record.flags |= SND_PCM1_FLG_ENABLE;
		pcm1->record.ack = snd_pcm1_interrupt_record;
		fs = snd_enter_user();
		if ((res = snd_pcm1_format(pcm1, SND_PCM1_RECORD, &format, 1)) < 0) {
			snd_printk("Oops.. Record format setup failed!!!\n");
			snd_leave_user(fs);
			pcm1->record.hw.close(pcm1);
			if (flags & SND_PCM1_LFLG_PLAY)
				pcm1->playback.hw.close(pcm1);
			return res;
		}
		memset(&rparams, 0, sizeof(rparams));
		rparams.fragment_size = 1024;
		rparams.fragments_min = 1;
		if ((res = snd_pcm1_record_params(pcm1, &rparams)) < 0) {
			snd_printk("Oops.. Record parameters setup failed!!!\n");
			snd_leave_user(fs);
			pcm1->record.hw.close(pcm1);
			if (flags & SND_PCM1_LFLG_PLAY)
				pcm1->playback.hw.close(pcm1);
			return res;
		}
		snd_leave_user(fs);
	}
	pcm1->flags |= flags;

#if 0
	printk("pcm1 open - done...\n");
#endif

	return 0;
}

static void snd_pcm1_close_card(snd_pcm1_t * pcm1, struct file *file)
{
	unsigned short flags;

	if (!pcm1)
		return;
	flags = snd_pcm1_file_flags(file) & pcm1->flags;
#if 0
	snd_printk("snd_release_pcm - flags = %i\n", flags);
#endif
	if (flags & SND_PCM1_LFLG_PLAY) {
		snd_pcm1_flush_playback(pcm1, 1);	/* synchronize playback */
		pcm1->playback.hw.trigger(pcm1, 0);	/* for sure */
		pcm1->playback.hw.close(pcm1);
		pcm1->flags &= ~SND_PCM1_LFLG_PLAY;
	}
	if (flags & SND_PCM1_LFLG_RECORD) {
		snd_pcm1_drain_record(pcm1);		/* and record, data can't be read, so flush isn't needed */
		pcm1->record.hw.trigger(pcm1, 0);	/* for sure */
		pcm1->record.hw.close(pcm1);
		pcm1->flags &= ~SND_PCM1_LFLG_RECORD;
	}
#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_pcm1_t *pcm1;

	pcm = snd_pcm_devices[(cardnum * SND_PCM_DEVICES) + device];
	if (!pcm)
		return -ENODEV;
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	if (!pcm1)
		return -ENODEV;
	snd_mutex_down(pcm1, open);
	if ((res = snd_pcm1_open_card(pcm1, file)) < 0) {
		snd_mutex_up(pcm1, open);
		return res;
	}
	file->private_data = pcm1;
	MOD_INC_USE_COUNT;
	pcm1->card->use_inc(pcm1->card);
	snd_mutex_up(pcm1, open);
	return 0;
}

static int snd_pcm1_release(unsigned short minor, int cardnum,
			    int device, struct file *file)
{
	snd_pcm1_t *pcm1;

	if (file->private_data) {
		pcm1 = (snd_pcm1_t *) file->private_data;
		snd_mutex_down(pcm1, open);
		snd_pcm1_close_card(pcm1, file);
		snd_mutex_up(pcm1, open);
		pcm1->card->use_dec(pcm1->card);
	}
	MOD_DEC_USE_COUNT;
	return 0;
}

static int snd_pcm1_ioctl(struct file *file,
			  unsigned int cmd, unsigned long arg)
{
	snd_pcm1_t *pcm1;
	snd_card_t *card;
	unsigned short flags;
	int tmp;

	pcm1 = (snd_pcm1_t *) file->private_data;
	card = pcm1->card;
	if (((cmd >> 8) & 0xff) != 'A')
		return -EIO;
	flags = snd_pcm1_file_flags(file);
#if 0
	printk("cmd = 0x%x, arg = 0x%x, flags = 0x%x\n", cmd, snd_ioctl_in((long *) arg), flags);
#endif
	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(pcm1, (snd_pcm_info_t *) arg);
	case SND_PCM_IOCTL_PLAYBACK_INFO:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		return snd_pcm1_playback_info(pcm1, (snd_pcm_playback_info_t *) arg);
	case SND_PCM_IOCTL_RECORD_INFO:
		if (!(flags & SND_PCM1_LFLG_RECORD))
			return -EIO;
		return snd_pcm1_record_info(pcm1, (snd_pcm_record_info_t *) arg);
	case SND_PCM_IOCTL_PLAYBACK_FORMAT:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		return snd_pcm1_format(pcm1, SND_PCM1_PLAYBACK, (snd_pcm_format_t *) arg, 0);
	case SND_PCM_IOCTL_RECORD_FORMAT:
		if (!(flags & SND_PCM1_LFLG_RECORD))
			return -EIO;
		return snd_pcm1_format(pcm1, SND_PCM1_RECORD, (snd_pcm_format_t *) arg, 0);
	case SND_PCM_IOCTL_PLAYBACK_PARAMS:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		return snd_pcm1_playback_params(pcm1, (snd_pcm_playback_params_t *) arg);
	case SND_PCM_IOCTL_RECORD_PARAMS:
		if (!(flags & SND_PCM1_LFLG_RECORD))
			return -EIO;
		return snd_pcm1_record_params(pcm1, (snd_pcm_record_params_t *) arg);
	case SND_PCM_IOCTL_PLAYBACK_STATUS:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		return snd_pcm1_playback_status(pcm1, (snd_pcm_playback_status_t *) arg);
	case SND_PCM_IOCTL_RECORD_STATUS:
		if (!(flags & SND_PCM1_LFLG_RECORD))
			return -EIO;
		return snd_pcm1_record_status(pcm1, (snd_pcm_record_status_t *) arg);
	case SND_PCM_IOCTL_DRAIN_PLAYBACK:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		return snd_pcm1_drain_playback(pcm1);
	case SND_PCM_IOCTL_FLUSH_PLAYBACK:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		return snd_pcm1_flush_playback(pcm1, 0);
	case SND_PCM_IOCTL_FLUSH_RECORD:
		if (!(flags & SND_PCM1_LFLG_RECORD))
			return -EIO;
		return snd_pcm1_flush_record(pcm1);
	case SND_PCM_IOCTL_PLAYBACK_PAUSE:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		return snd_pcm1_playback_pause(pcm1, snd_ioctl_in((long *) arg));
	case SND_PCM_IOCTL_PLAYBACK_TIME:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		tmp = snd_ioctl_in((long *) arg);
		if (tmp)
			pcm1->playback.flags |= SND_PCM1_FLG_TIME;
		else
			pcm1->playback.flags &= ~SND_PCM1_FLG_TIME;
		snd_ioctl_out((long *) arg, tmp != 0);
		return 0;
	case SND_PCM_IOCTL_RECORD_TIME:
		if (!(flags & SND_PCM1_LFLG_RECORD))
			return -EIO;
		tmp = snd_ioctl_in((long *) arg);
		if (tmp)
			pcm1->record.flags |= SND_PCM1_FLG_TIME;
		else
			pcm1->record.flags &= ~SND_PCM1_FLG_TIME;
		snd_ioctl_out((long *) arg, tmp != 0);
		return 0;
#ifdef CONFIG_SND_DEBUG
	default:
		snd_printk("pcm: unknown command = 0x%x\n", cmd);
#endif
	}
	return -EIO;
}

static int snd_pcm1_control_ioctl(snd_card_t * card, snd_control_t * control,
		                  unsigned int cmd, unsigned long arg)
{
	unsigned int tmp;
	snd_pcm1_t *pcm1;

	tmp = card->number << 2;
	switch (cmd) {
	case SND_CTL_IOCTL_PCM_INFO:
		pcm1 = (snd_pcm1_t *) snd_pcm_devices[tmp + control->pcm_device]->private_data;
		return snd_pcm1_info(pcm1, (snd_pcm_info_t *) arg);
	case SND_CTL_IOCTL_PCM_PLAYBACK_INFO:
		pcm1 = (snd_pcm1_t *) snd_pcm_devices[tmp + control->pcm_device]->private_data;
		return snd_pcm1_playback_info(pcm1, (snd_pcm_playback_info_t *) arg);
	case SND_CTL_IOCTL_PCM_RECORD_INFO:
		pcm1 = (snd_pcm1_t *) snd_pcm_devices[tmp + control->pcm_device]->private_data;
		return snd_pcm1_record_info(pcm1, (snd_pcm_record_info_t *) arg);
	}
	return -EAGAIN;
}

static long snd_pcm1_read(struct file *file, char *buf, long count)
{
	snd_pcm1_t *pcm1;

	pcm1 = (snd_pcm1_t *) file->private_data;
	if (!pcm1)
		return -EIO;
	if (!(snd_pcm1_file_flags(file) & SND_PCM1_LFLG_RECORD) ||
	    !(pcm1->flags & SND_PCM1_LFLG_RECORD))
		return -EIO;
	return snd_pcm1_buffer_to_user(file, pcm1, buf, count);
}

static long snd_pcm1_write(struct file *file, const char *buf, long count)
{
	snd_pcm1_t *pcm1;

	pcm1 = (snd_pcm1_t *) file->private_data;
	if (!pcm1)
		return -EIO;
	if (!(snd_pcm1_file_flags(file) & SND_PCM1_LFLG_PLAY) ||
	    !(pcm1->flags & SND_PCM1_LFLG_PLAY))
		return -EIO;
	return snd_pcm1_user_to_buffer(file, pcm1, buf, count);
}

#ifdef SND_POLL
static unsigned int snd_pcm1_poll(struct file *file, poll_table * wait)
{
	snd_pcm1_t *pcm1;
	unsigned long flags;
	unsigned int mask;
	snd_pcm1_channel_t *record, *playback;
	unsigned short fflags;

	fflags = snd_pcm1_file_flags(file);
	pcm1 = (snd_pcm1_t *) file->private_data;
	if (!pcm1)
		return 0;

	record = &pcm1->record;
	playback = &pcm1->playback;

	if (fflags & SND_PCM1_LFLG_RECORD) {
		snd_pcm1_trigger_record(pcm1, record);
		snd_spin_lock(record, sleep_lock, &flags);
		snd_getlock(record, sleep) |= SND_WK_SLEEP;
		snd_poll_wait(file, record, sleep, wait);
		snd_spin_unlock(record, sleep_lock, &flags);
	}
	if (fflags & SND_PCM1_LFLG_PLAY) {
		snd_spin_lock(record, sleep_lock, &flags);
		snd_getlock(record, sleep) |= SND_WK_SLEEP;
		snd_poll_wait(file, playback, sleep, wait);
		snd_spin_unlock(record, sleep_lock, &flags);
	}
	mask = 0;
	if (fflags & SND_PCM1_LFLG_RECORD) {
		if (snd_pcm1_record_ok(record))
			mask |= POLLIN | POLLRDNORM;
	}
	if (fflags & SND_PCM1_LFLG_PLAY) {
		if (snd_pcm1_playback_ok(playback))
			mask |= POLLOUT | POLLWRNORM;
	}
	return mask;
}
#else
static int snd_pcm1_select(struct file *file,
			   int sel_type,
		           select_table * wait)
{
	snd_pcm1_t *pcm1;
	unsigned long flags;
	snd_pcm1_channel_t *pchn;
	unsigned short fflags;

	fflags = snd_pcm1_file_flags(file);
	pcm1 = (snd_pcm1_t *) file->private_data;
	if (!pcm1)
		return -EIO;
	switch (sel_type) {
	case SEL_IN:
		if (!(fflags & SND_PCM1_LFLG_RECORD))
			return 0;
		pchn = &pcm1->record;
		snd_pcm1_trigger_record(pcm1, pchn);
		snd_spin_lock(pchn, sleep_lock, &flags);
		if (!snd_pcm1_record_ok(pchn)) {
			snd_getlock(pchn, sleep) |= SND_WK_SLEEP;
			snd_select_wait(pchn, sleep, wait);
			snd_spin_unlock(pchn, sleep_lock, &flags);
			return 0;
		}
		snd_getlock(pchn, sleep) &= ~SND_WK_SLEEP;
		snd_spin_unlock(pcm1, record_sleep, &flags);
		return 1;
	case SEL_OUT:
		if (!(fflags & SND_PCM1_LFLG_PLAY))
			return 0;
		pchn = &pcm1->playback;
		snd_spin_lock(pchn, sleep_lock, &flags);
		if (!snd_pcm1_playback_ok(pchn)) {
			snd_getlock(pchn, sleep) |= SND_WK_SLEEP;
			snd_select_wait(pchn, sleep, wait);
			snd_spin_unlock(pchn, sleep_lock, &flags);
			return 0;
		}
		snd_getlock(pchn, sleep) &= ~SND_WK_SLEEP;
		snd_spin_unlock(pchn, sleep_lock, &flags);
		return 1;
	case SEL_EX:
		break;
	}
	return 0;
}
#endif

/*
 *  /proc interface
 */

void snd_pcm1_proc_format(snd_pcm_channel_t *pchn, snd_pcm1_channel_t *pchn1)
{
	snd_pcm_format_t format;
	unsigned int mode;
	
	memset(&format, 0, sizeof(format));
	mode = pchn1->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 = pchn1->rate;
	format.channels = pchn1->voices;
	snd_pcm_proc_format(pchn, &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_read(snd_info_buffer_t * buffer, void *private_data)
{
	snd_pcm1_t *pcm1;

	pcm1 = (snd_pcm1_t *) private_data;
	snd_iprintf(buffer, "%s\n\n", pcm1->pcm->name);
	if (pcm1->pcm->info_flags & SND_PCM_INFO_PLAYBACK) {
		if (pcm1->flags & SND_PCM1_LFLG_PLAY) {
			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",
				    pcm1->flags & SND_PCM1_LFLG_OSS_PLAY ? "OSS compatible" : "native",
				    pcm1->playback.flags & SND_PCM1_FLG_MMAP ? " [mmap]" : "",
			   snd_pcm1_proc_get_format(pcm1->playback.mode),
			   pcm1->playback.real_rate, pcm1->playback.rate,
				    pcm1->playback.voices,
				    pcm1->playback.size,
				    pcm1->playback.used_size,
				    pcm1->playback.blocks,
				    pcm1->playback.block_size);
			if (!(pcm1->flags & SND_PCM1_LFLG_OSS_PLAY)) {
				snd_iprintf(buffer,
					    "  Fragments room : %d\n"
					    "  Fragments max  : %d\n",
					    pcm1->playback.blocks_room,
					    pcm1->playback.blocks_max);
			}
			snd_iprintf(buffer,
				    "  Underruns      : %d\n"
				    "  Total underruns: %d\n",
				    pcm1->playback.xruns,
				    pcm1->playback.total_xruns);
		} else {
			snd_iprintf(buffer, "Playback isn't active.\n");
		}
	}
	if (pcm1->pcm->info_flags & SND_PCM_INFO_RECORD) {
		if (pcm1->flags & SND_PCM1_LFLG_RECORD) {
			snd_iprintf(buffer,
				    "Record\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",
				    pcm1->flags & SND_PCM1_LFLG_OSS_RECORD ? "OSS compatible" : "native",
				    pcm1->playback.flags & SND_PCM1_FLG_MMAP ? " [mmap]" : "",
			     snd_pcm1_proc_get_format(pcm1->record.mode),
			       pcm1->record.real_rate, pcm1->record.rate,
				    pcm1->record.voices,
				    pcm1->record.size,
				    pcm1->record.used_size,
				    pcm1->record.blocks,
				    pcm1->record.block_size);
			if (!(pcm1->flags & SND_PCM1_LFLG_OSS_RECORD)) {
				snd_iprintf(buffer,
					    "  Fragments min  : %d\n",
					    pcm1->record.blocks_min);
			}
			snd_iprintf(buffer,
				    "  Overruns       : %d\n"
				    "  Total overruns : %d\n",
				    pcm1->record.xruns,
				    pcm1->record.total_xruns);
			if (pcm1->record.hw.flags & SND_PCM1_HW_OVERRANGE)
				snd_iprintf(buffer,
					    "  Overrange      : %d\n",
					    pcm1->record.overrange);
		} else {
			snd_iprintf(buffer, "Record isn't active.\n");
		}
	}
}

static void snd_pcm1_proc_init(snd_pcm1_t * pcm1)
{
	char name[16];
	snd_info_entry_t *entry;

	sprintf(name, "pcm%d", pcm1->pcm->device);
	entry = snd_info_create_entry(pcm1->card, name);
	if (entry) {
		entry->private_data = pcm1;
		entry->t.text.read_size = 1024;
		entry->t.text.read = snd_pcm1_proc_info_read;
		if (snd_info_register(entry) < 0) {
			snd_info_free_entry(entry);
			entry = NULL;
		}
		pcm1->proc_entry = entry;
	}
}

static void snd_pcm1_proc_done(snd_pcm1_t * pcm1)
{
	if (pcm1->proc_entry) {
		snd_info_unregister(pcm1->proc_entry);
		pcm1->proc_entry = NULL;
	}
}

/*
 *  Register functions
 */

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

static void snd_pcm1_timer_start(snd_timer_t * timer)
{
	unsigned long flags;
	snd_pcm1_channel_t * pchn;
	
	pchn = (snd_pcm1_channel_t *)timer->private_data;
	snd_spin_lock(pchn, lock, &flags);
	pchn->flags |= SND_PCM1_FLG_TIMER;
	snd_spin_unlock(pchn, lock, &flags);
}

static void snd_pcm1_timer_stop(snd_timer_t * timer)
{
	unsigned long flags;
	snd_pcm1_channel_t * pchn;
	
	pchn = (snd_pcm1_channel_t *)timer->private_data;
	snd_spin_lock(pchn, lock, &flags);
	pchn->flags &= ~SND_PCM1_FLG_TIMER;
	snd_spin_unlock(pchn, 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 */
};

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 */
#ifdef SND_POLL
	snd_pcm1_poll,		/* poll */
#else
	snd_pcm1_select,	/* select */
#endif
	snd_pcm1_ioctl,		/* ioctl */
	NULL,			/* mmap */
};

static void snd_pcm1_free(void *data)
{
	snd_pcm1_t *pcm1;

	pcm1 = (snd_pcm1_t *) data;
	if (pcm1->playback.hw.private_free)
		pcm1->playback.hw.private_free(pcm1->playback.hw.private_data);
	if (pcm1->record.hw.private_free)
		pcm1->record.hw.private_free(pcm1->record.hw.private_data);
	if (pcm1->private_free)
		pcm1->private_free(pcm1->private_data);
	snd_free(pcm1, sizeof(snd_pcm1_t));
}

snd_pcm_t *snd_pcm1_new_device(snd_card_t * card, char *id)
{
	snd_pcm_t *pcm;
	snd_pcm1_t *pcm1;
	snd_pcm1_channel_t *playback, *record;

	if (!card)
		return NULL;
	pcm = snd_pcm_new_device(card, id, &snd_pcm1_reg);
	if (!pcm)
		return NULL;
	pcm1 = (snd_pcm1_t *) snd_calloc(sizeof(snd_pcm1_t));
	if (!pcm1) {
		snd_pcm_free(pcm);
		return NULL;
	}
	pcm1->flags = SND_PCM1_LFLG_NONE;
	pcm1->card = card;
	pcm1->pcm = pcm;
	snd_mutex_prepare(pcm1, open);
	playback = &pcm1->playback;
	record = &pcm1->record;
	snd_sleep_prepare(playback, sleep);
	snd_sleep_prepare(record, sleep);
	snd_spin_prepare(playback, lock);
	snd_spin_prepare(record, lock);
	snd_spin_prepare(playback, sleep_lock);
	snd_spin_prepare(record, sleep_lock);
	snd_mutex_prepare(playback, setup_mutex);
	snd_mutex_prepare(record, setup_mutex);
	snd_pcm1_clear_channel(playback);
	snd_pcm1_clear_channel(record);
	pcm->type = SND_PCM_TYPE_1;
	pcm->private_data = pcm1;
	pcm->private_free = snd_pcm1_free;
	return pcm;
}

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

static int snd_pcm1_register_notify(unsigned short minor, snd_pcm_t * pcm)
{
	snd_pcm1_t *pcm1;

	if (pcm->type != SND_PCM_TYPE_1)
		return -EINVAL;
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	snd_pcm1_proc_init(pcm1);
	if (pcm->info_flags & SND_PCM_INFO_PLAYBACK)
		snd_pcm1_timer_init(pcm1, &pcm1->playback, 0);
	if (pcm->info_flags & SND_PCM_INFO_RECORD)
		snd_pcm1_timer_init(pcm1, &pcm1->record, 1);
	return 0;
}

static int snd_pcm1_unregister_notify(unsigned short minor, snd_pcm_t * pcm)
{
	snd_pcm1_t *pcm1;

	if (pcm->type != SND_PCM_TYPE_1)
		return -EINVAL;
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	if (pcm1->playback.timer) {
		snd_timer_unregister(pcm1->playback.timer);
		pcm1->playback.timer = NULL;
	}
	if (pcm1->record.timer) {
		snd_timer_unregister(pcm1->record.timer);
		pcm1->record.timer = NULL;
	}
	snd_pcm1_proc_done(pcm1);
	return 0;
}

/*
 *  ENTRY functions
 */

#ifndef LINUX_2_1
extern struct symbol_table snd_symbol_table_pcm1_export;
#endif

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

int init_module(void)
{
#ifndef LINUX_2_1
	if (register_symtab(&snd_symbol_table_pcm1_export) < 0)
		return -ENOMEM;
#endif
	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);
}
