/*
 *  Digital Audio (PCM) abstract layer / OSS compatible
 *  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.
 *
 * --
 *
 * Tue May  4 19:16:45 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk>
 *   When trying to set stereo on a mono card returned error where OSS would
 *   have returned the available number of channels. Fixed.
 */

#define __SND_OSS_COMPAT__
#define SND_MAIN_OBJECT_FILE
#include "driver.h"
#include "minors.h"
#include "pcm1.h"
#include "info.h"
#include "ulaw.h"

#ifdef LINUX_2_1
EXPORT_NO_SYMBOLS;
#endif

#if 0
#define SND_PCM1_DEBUG_BUFFERS
#endif

#if 0
int snd_mixer_ioctl_card(snd_card_t * card, struct file *file,
                         unsigned int cmd, unsigned long arg);
#endif

static void snd_pcm1_oss_compute_blocks(snd_pcm1_t * pcm1, int direction);
static int snd_pcm1_oss_nonblock(snd_pcm1_t * pcm1, int flags);

/*
 *  interrupt callbacks from lowlevel driver
 */

static void snd_pcm1_oss_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_pcm1_oss_interrupt_playback: Oops, playback interrupt when playback isn't active!!!\n");
		return;
	}
	if (!(pchn->flags & SND_PCM1_FLG_MMAP)) {
		/* try clear first few bytes from non-active block */
		if (pchn->hw.flags & SND_PCM1_HW_AUTODMA)
			pchn->hw.dma_neutral(pcm1, pchn->buffer,
					     pchn->tail * pchn->block_size,
					     16, pchn->neutral_byte);
		if (pchn->used == 1) {
			if (!(pchn->flags & SND_PCM1_FLG_SYNC)) {
#ifdef SND_PCM1_DEBUG_BUFFERS
				printk("playback: underrun!!! - jiffies = %li\n", jiffies);
#endif
				pchn->total_xruns++;
				pchn->xruns++;
			}
		}
		snd_spin_lock(pchn, lock, &flags);
		pchn->interrupts++;
		if (pchn->used > 0) {
			pchn->processed_bytes += pchn->block_size;
			pchn->used--;
		}
		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(pchn, lock, &flags);
	} else {
		snd_spin_lock(pchn, lock, &flags);
		pchn->interrupts++;
		pchn->processed_bytes += pchn->block_size;
		pchn->tail++;
		pchn->tail %= pchn->blocks;
		snd_spin_unlock(pchn, lock, &flags);
		trigger = 1;
	}
	if (trigger && !(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);
	}
	if (!trigger) {
		if (pchn->hw.flags & SND_PCM1_HW_AUTODMA) {
			pchn->hw.trigger(pcm1, 0);
		}
		pchn->flags &= ~SND_PCM1_FLG_TRIGGER;
	}
	if (snd_getlock(pchn, sleep) & SND_WK_SLEEP) {
		snd_wakeup(pchn, sleep);
		snd_getlock(pchn, sleep) &= ~SND_WK_SLEEP;
	}
}

static void snd_pcm1_oss_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_pcm1_oss_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->flags & SND_PCM1_FLG_MMAP)) {
		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);
	} else {
		pchn->interrupts++;
		pchn->processed_bytes += pchn->block_size;
		pchn->head++;
		pchn->head %= pchn->blocks;
	}
	snd_spin_unlock(pchn, lock, &flags);
	if (trigger && !(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);
	}
	if (!trigger) {
		if (pchn->hw.flags & SND_PCM1_HW_AUTODMA) {
			pchn->hw.trigger(pcm1, 0);
		}
		pchn->flags &= ~SND_PCM1_FLG_TRIGGER;
	}
	if (snd_getlock(pchn, sleep) & SND_WK_SLEEP) {
		snd_getlock(pchn, sleep) &= ~SND_WK_SLEEP;
		snd_wakeup(pchn, sleep);
	}
}

/*
 *  trigger standard buffered playback
 */

static void snd_pcm1_oss_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_TRIGGERA;
		if (pchn->tail != 0)
			snd_printd("Oops, playback start when tail = %i\n", pchn->tail);
		pchn->lastxruns = pchn->xruns;
		snd_spin_unlock(pchn, lock, &flags);
		pchn->hw.prepare(pcm1,
				 pchn->buffer, pchn->used_size,
				 pchn->tail * pchn->block_size,
				 pchn->block_size);
		pchn->hw.trigger(pcm1, 1);
		snd_spin_lock(pchn, lock, &flags);
		snd_pcm1_lock(pchn, pchn->tail);
	}
	snd_spin_unlock(pchn, lock, &flags);
}

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

static void snd_pcm1_oss_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_oss_user_to_buffer(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;
#if 0
	snd_printk("snd_pcm1_oss_user_to_buffer: buf=0x%x, count=0x%x,%i (%s), used = %i, jiffies = %li\n", (int) buf, count, count, pchn->flags & SND_PCM1_FLG_NONBLK ? "non blocked" : "blocked", pchn->used, jiffies);
#endif
	count &= ~((pchn->voices * (pchn->mode & SND_PCM1_MODE_16 ? 2 : 1)) - 1);
	pchn->flags &= ~SND_PCM1_FLG_ABORT;
	if (pchn->flags & SND_PCM1_FLG_MMAP)
		return -EIO;	/* go to hell... */
	if (pchn->flags & SND_PCM1_FLG_BUFFERS)
		snd_pcm1_oss_compute_blocks(pcm1, SND_PCM1_PLAYBACK);
	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 (pchn->used >= pchn->blocks) {
			if (!(pchn->flags & SND_PCM1_FLG_TRIGGER))
				snd_pcm1_oss_trigger_playback(pcm1, pchn);
			if (pchn->flags & SND_PCM1_FLG_NONBLK)
				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)) {
				pchn->flags |= SND_PCM1_FLG_ABORT;
				return result;
			}
			if (pchn->used >= pchn->blocks && snd_timeout(pchn, sleep)) {
				snd_printd("pcm1_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_oss_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)
			snd_pcm1_oss_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);
	}
	/* hmm. I'm not sure that this is same as in OSS */
	/* if bellow two lines are removed - RealVideo works much better */
	if (pchn->used > 0)
		snd_pcm1_oss_trigger_playback(pcm1, pchn);
	return result;
}

/*
 *  trigger standard buffered record
 */

static void snd_pcm1_oss_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;
		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);
		snd_spin_unlock(pchn, lock, &flags);
		pchn->hw.prepare(pcm1,
				 pchn->buffer, pchn->used_size,
				 pchn->head * pchn->block_size,
				 pchn->block_size);
		pchn->hw.trigger(pcm1, 1);
		snd_spin_lock(pchn, lock, &flags);
		snd_pcm1_lock(pchn, pchn->head);
	}
	snd_spin_unlock(pchn, lock, &flags);
}

/*
 *  dma to user
 */

static int snd_pcm1_oss_buffer_to_user(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 &= ~((pchn->voices * (pchn->mode & SND_PCM1_MODE_16 ? 2 : 1)) - 1);
	pchn->flags &= ~SND_PCM1_FLG_ABORT;
	if (pchn->flags & SND_PCM1_FLG_MMAP)
		return -EIO;
	if (pchn->flags & SND_PCM1_FLG_BUFFERS)
		snd_pcm1_oss_compute_blocks(pcm1, SND_PCM1_RECORD);
	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 (!pchn->used) {
			snd_pcm1_oss_trigger_record(pcm1, pchn);
			if (pchn->flags & SND_PCM1_FLG_NONBLK)
				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)) {
				pchn->flags |= SND_PCM1_FLG_ABORT;
				return -EINTR;
			}
			if (!pchn->used && snd_timeout(pchn, sleep)) {
				snd_printd("snd_pcm1_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 void snd_pcm1_oss_sync_playback(snd_pcm1_t * pcm1)
{
	unsigned long flags;
	snd_pcm1_channel_t *pchn;

	pchn = &pcm1->playback;
	if (pchn->flags & SND_PCM1_FLG_ABORT) {
		if (pchn->flags & SND_PCM1_FLG_TRIGGER) {
			pchn->hw.trigger(pcm1, 0);
			pchn->flags &= ~SND_PCM1_FLG_TRIGGER;
		}
		goto __end_done;
	}
	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)
			snd_pcm1_oss_playback_underrun(pcm1, pchn);
		size = pchn->frag_size;
		count = pchn->block_size - size;
		offset = 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)
		goto __end;	/* probably mmaped access */

	snd_pcm1_oss_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)) {
			pchn->flags |= SND_PCM1_FLG_ABORT;
			pchn->hw.trigger(pcm1, 0);
			pchn->flags &= ~SND_PCM1_FLG_TRIGGER;
			goto __end_done;
		}
		if (pchn->used && snd_timeout(pchn, sleep)) {
			snd_printd("snd_pcm1_oss_sync_playback: timeout, skipping waiting blocks\n");
			return;
		}
	}

      __end_done:
	pchn->flags |= SND_PCM1_FLG_NEUTRAL;
	snd_pcm1_fill_with_neutral(pcm1, pchn);
	pchn->flags &= ~SND_PCM1_FLG_TRIGGER1;
	pchn->head = pchn->tail = pchn->used = 0;
	pchn->frag_size = 0;
	snd_pcm1_lockzero(pchn);
      __end:
	pchn->flags &= ~SND_PCM1_FLG_SYNC;
}

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

	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)) {
			pchn->flags |= SND_PCM1_FLG_ABORT;
			pchn->hw.trigger(pcm1, 0);
			pchn->flags &= ~(SND_PCM1_FLG_SYNC | SND_PCM1_FLG_TRIGGER);
			return;
		}
		if ((pchn->flags & SND_PCM1_FLG_TRIGGER) && snd_timeout(pchn, sleep)) {
			snd_printd("snd_pcm1_sync_record: sync timeout\n");
			pchn->flags &= ~SND_PCM1_FLG_SYNC;
			break;
		}
	}
	pchn->flags &= ~SND_PCM1_FLG_SYNC;
	snd_pcm1_fill_with_neutral(pcm1, pchn);
	pchn->flags &= ~SND_PCM1_FLG_TRIGGER1;
	pchn->head = pchn->tail = pchn->used = 0;
	pchn->frag_size = pchn->block_size;
	snd_pcm1_lockzero(pchn);
}

static int snd_pcm1_oss_sync(snd_pcm1_t * pcm1, unsigned short flags)
{
	if (flags & SND_PCM1_LFLG_PLAY)
		snd_pcm1_oss_sync_playback(pcm1);
	if (flags & SND_PCM1_LFLG_RECORD)
		snd_pcm1_oss_sync_record(pcm1);
	return 0;
}

/*
 *  other things
 */

static void snd_pcm1_oss_compute_blocks(snd_pcm1_t * pcm1, int direction)
{
	unsigned int block, blocks, bps, min;
	unsigned long flags;
	snd_pcm1_channel_t *pchn;

	if (direction == SND_PCM1_PLAYBACK) {
		pchn = &pcm1->playback;
		snd_pcm1_oss_sync_playback(pcm1);
	} else {
		pchn = &pcm1->record;
		snd_pcm1_oss_sync_record(pcm1);
	}
	snd_mutex_down(pchn, setup_mutex);
	snd_spin_lock(pchn, lock, &flags);
	pchn->used_size = pchn->mmap_size > 0 ? pchn->mmap_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;
	}
#ifdef SND_PCM1_DEBUG_BUFFERS
	printk("pchn -> used_size = %i, size = %i, mode = 0x%x\n", pchn->used_size, pchn->size, pchn->mode);
#endif
	for (min = 1, bps = pchn->hw.min_fragment; bps > 1; bps--)
		min <<= 1;
	/* load another values, if user need it */
	if (pchn->setup) {
		if (pchn->setup->fragment_size)
			pchn->requested_block_size = pchn->setup->fragment_size;
		if (pchn->setup->fragments)
			pchn->requested_blocks = pchn->setup->fragments;
	}
	if (pchn->requested_block_size > 0) {
		block = pchn->requested_block_size;
		block &= ~(pchn->hw.align | 3);
		if (block < min)
			block = min;
		pchn->hw.ioctl(pcm1, SND_PCM1_IOCTL_FRAG, (unsigned long *)&block);
		blocks = pchn->used_size / block;
		if (blocks > pchn->requested_blocks)
			blocks = pchn->requested_blocks;
	} else {
		bps = pchn->rate * pchn->voices;
		if (pchn->mode & SND_PCM1_MODE_16)
			bps <<= 1;
		if (pchn->mode & SND_PCM1_MODE_ADPCM)
			bps >>= 2;
		bps >>= 2;	/* make only fragment for 0.25 sec */
		if (bps <= 0)
			bps = 16;
		block = pchn->used_size;
		while (block > bps)
			block >>= 1;
		if (block == pchn->used_size)
			block >>= 1;
#if 0
		if (record)
			block /= 4;	/* small fragment when recording */
#endif
		block &= ~(pchn->hw.align | 3);		/* align to 4 */
		if (block < min)
			block = min;
		pchn->hw.ioctl(pcm1, SND_PCM1_IOCTL_FRAG, (unsigned long *)&block);
		blocks = pchn->used_size / block;
#if 0
		if (blocks > 8)
			blocks = 8;	/* max 2 sec buffer */
#endif
	}
#if 0
	max = pchn->hw_max_dma_block(card);
	if (max < block)
		block = max;
#endif
	if (blocks < 2) {
		blocks = 2;
		if (block > pchn->used_size >> 1)
			block = pchn->used_size >> 1;
	}
	if (blocks > 128)
		blocks = 128;
	pchn->used_size = blocks * block;
	pchn->blocks = blocks;
	pchn->block_size = block;
	pchn->flags |= SND_PCM1_FLG_NEUTRAL;
	pchn->flags &= ~SND_PCM1_FLG_BUFFERS;
	if (direction == SND_PCM1_RECORD) {
		pchn->frag_size = pchn->block_size;
	}
	snd_spin_unlock(pchn, lock, &flags);
	snd_mutex_up(pchn, setup_mutex);
	snd_pcm1_proc_format(direction == SND_PCM1_PLAYBACK ?
				&pcm1->pcm->playback : &pcm1->pcm->record,
			     pchn);
#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("used_size = %i, blocks = %i, blocks_size = %i, mmap = %s\n", pchn->used_size, pchn->blocks, pchn->block_size, pchn->flags & SND_PCM1_FLG_MMAP ? "yes" : "no");
#endif
}

static int snd_pcm1_oss_set_subdivision(snd_pcm1_t * pcm1, unsigned short flags,
                                        unsigned int subdivision)
{
	int idx;
	snd_pcm1_channel_t *pchn;

	for (idx = 1; idx <= 2; idx++)
		if (flags & idx) {
			pchn = idx == 1 ? &pcm1->playback : &pcm1->record;
			if (pchn->requested_subdivision != subdivision) {
				if (idx == 1)
					snd_pcm1_oss_sync_playback(pcm1);
				else
					snd_pcm1_oss_sync_record(pcm1);
				pchn->requested_subdivision = subdivision;
				pchn->flags |= SND_PCM1_FLG_NEUTRAL |
				               SND_PCM1_FLG_BUFFERS;
			}
		}
	return 0;
}

/*
 * SET FRAGMENT notes:
 *   Real Video 5.0b3 - (1) fragment = 0x2e00004 (rate & format set before)
 *                      (2) fragment = 0x04 (rate & format set before)
 *   Libmmoss 1.1 - fragment = 0x20009 (rate & format set before)
 *   MTV - fragment = 0x7f000a (rate & format set after!!)
 */

static int snd_pcm1_oss_set_fragment(snd_pcm1_t * pcm1, unsigned short flags, unsigned int fragment)
{
	int idx, size, size1, bytes, count, min_fragment;
	snd_pcm1_channel_t *pchn;

	size = 0x10000;
	count = (fragment >> 16) & 0xffff;
	bytes = fragment & 0xffff;
	if (!bytes) {
		if (flags & SND_PCM1_LFLG_PLAY)
			size = pcm1->playback.requested_block_size;
		if (flags & SND_PCM1_LFLG_RECORD)
			size = pcm1->record.requested_block_size;
		if (!size)
			size = 0x10000;
		size1 = size;
		while (size1 > 0) {
			bytes++;
			size1 >>= 1;
		}
	} else {
		min_fragment = 4;
		if (flags & SND_PCM1_LFLG_PLAY)
			if (pcm1->playback.hw.min_fragment > min_fragment)
				min_fragment = pcm1->playback.hw.min_fragment;
		if (flags & SND_PCM1_LFLG_RECORD)
			if (pcm1->record.hw.min_fragment > min_fragment)
				min_fragment = pcm1->record.hw.min_fragment;
		if (bytes < min_fragment)
			bytes = min_fragment;
		if (bytes > 17)
			bytes = 17;
		size = 1 << bytes;
	}
	if (!count) {
		count = 128;
	} else {
		if (count < 2)
			count = 2;
		if (count > 128)
			count = 128;
	}

#ifdef SND_PCM1_DEBUG_BUFFERS
	printk("set fragment: fragment = 0x%x, size = %i, count = %i\n", fragment, size, count);
#endif

	for (idx = 1; idx <= 2; idx++)
		if (flags & idx) {
			pchn = idx == 1 ? &pcm1->playback : &pcm1->record;
			if (pchn->requested_block_size != size ||
			    pchn->requested_blocks != count) {
				if (idx == 1)
					snd_pcm1_oss_sync_playback(pcm1);
				else
					snd_pcm1_oss_sync_record(pcm1);
				pchn->requested_block_size = size;
				pchn->requested_blocks = count;
				pchn->flags |= SND_PCM1_FLG_NEUTRAL |
				               SND_PCM1_FLG_BUFFERS;
			}
			if (pchn->setup) {
				count = pchn->setup->fragments;
				bytes = 0;
				size = pchn->setup->fragment_size;
				while (size > 1) {
					bytes++;
					size >>= 1;
				}
			}
		}
#if 0
	printk("return - count = %i, bytes = %i\n", count, bytes);
#endif
	return (count << 16) | bytes;
}

static unsigned int snd_pcm1_oss_get_block_size(snd_pcm1_t * pcm1,
                                                unsigned short flags)
{
	snd_card_t *card;

	card = pcm1->card;
	if ((flags & SND_PCM1_LFLG_PLAY) &&
	    (pcm1->playback.flags & SND_PCM1_FLG_BUFFERS))
		snd_pcm1_oss_compute_blocks(pcm1, SND_PCM1_PLAYBACK);
	if ((flags & SND_PCM1_LFLG_RECORD) &&
	    (pcm1->record.flags & SND_PCM1_FLG_BUFFERS))
		snd_pcm1_oss_compute_blocks(pcm1, SND_PCM1_RECORD);
	return (flags & 1) ?
			pcm1->playback.block_size : pcm1->record.block_size;
}

static int snd_pcm1_oss_set_mode(snd_pcm1_t * pcm1, unsigned short flags,
                                  unsigned int mode)
{
	int idx;
	snd_pcm1_channel_t *pchn;

#ifdef SND_PCM1_DEBUG_BUFFERS
	printk("mode = 0x%x\n", mode);
#endif
	for (idx = 1; idx <= 2; idx++)
		if (flags & idx) {
			pchn = idx == 1 ? &pcm1->playback : &pcm1->record;
			if (pchn->mode != mode) {
				int err;
				if (idx == 1)
					snd_pcm1_oss_sync_playback(pcm1);
				else
					snd_pcm1_oss_sync_record(pcm1);
				pchn->neutral_byte = (mode & SND_PCM1_MODE_16) ?
								0x00 : 0x80;
				pchn->mode = mode;
				err = pchn->hw.ioctl(pcm1, SND_PCM1_IOCTL_MODE, NULL);
				if (err < 0 && err != -ENXIO)
					return err;
				pchn->flags |= SND_PCM1_FLG_NEUTRAL |
				               SND_PCM1_FLG_BUFFERS;
			}
		}
	return 0;
}

static unsigned int snd_pcm1_oss_get_mode(snd_pcm1_t * pcm1,
                                          unsigned short flags)
{
	return (flags & 1) ? pcm1->playback.mode : pcm1->record.mode;
}

static void snd_pcm1_oss_set_format(snd_pcm1_t * pcm1, unsigned short flags,
                                    unsigned int format)
{
	int idx;
	snd_pcm1_channel_t *pchn;

#ifdef SND_PCM1_DEBUG_BUFFERS
	printk("format = 0x%x\n", format);
#endif
	for (idx = 1; idx <= 2; idx++)
		if (flags & idx) {
			pchn = idx == 1 ? &pcm1->playback : &pcm1->record;
			pchn->format = format;
		}
}

static unsigned int snd_pcm1_oss_get_format(snd_pcm1_t * pcm1,
                                            unsigned short flags)
{
	return (flags & 1) ? pcm1->playback.format : pcm1->record.format;
}

static int snd_pcm1_oss_set_rate(snd_pcm1_t * pcm1, unsigned short flags,
                                  unsigned int rate)
{
	int idx;
	snd_pcm1_channel_t *pchn;

#ifdef SND_PCM1_DEBUG_BUFFERS
	printk("rate = %i\n", rate);
#endif
	for (idx = 1; idx <= 2; idx++)
		if (flags & idx) {
			pchn = idx == 1 ? &pcm1->playback : &pcm1->record;
			if (pchn->rate != rate) {
				int err;
				if (idx == 1)
					snd_pcm1_oss_sync_playback(pcm1);
				else
					snd_pcm1_oss_sync_record(pcm1);
				pchn->rate = rate;
				err = pchn->hw.ioctl(pcm1, SND_PCM1_IOCTL_RATE, NULL);
				if (err < 0 && err != -ENXIO)
					return err;
				pchn->flags |= SND_PCM1_FLG_BUFFERS;
			}
		}
	return 0;
}

static unsigned int snd_pcm1_oss_get_rate(snd_pcm1_t * pcm1,
                                          unsigned short flags)
{
	unsigned int result;

	result = (flags & 1) ? pcm1->playback.real_rate :
	                       pcm1->record.real_rate;
#ifdef SND_PCM1_DEBUG_BUFFERS
	printk("get rate = %i\n", result);
#endif
	return result;
}

static int snd_pcm1_oss_format(snd_pcm1_t * pcm1, unsigned short flags,
                               int format)
{
	unsigned int new_mode;
	unsigned int new_format;
	int err;

	if (format != SND_PCM_FMT_QUERY) {
#if 0
		printk("format = 0x%x\n", format);
#endif
		if (!format) {
			new_format = ~snd_pcm1_oss_get_format(pcm1, flags);
			new_mode = snd_pcm1_oss_get_mode(pcm1, flags);
		} else {
			new_format = 0;
			if (flags & SND_PCM1_LFLG_PLAY) {
				new_format = format & pcm1->playback.hw.formats;
				if (!new_format)	/* not supported format */
					new_format = SND_PCM_FMT_U8;
			}
			if ((flags & SND_PCM1_LFLG_RECORD) &&
			  (!(pcm1->playback.flags & SND_PCM1_FLG_MMAP) ||
			   (pcm1->record.flags & SND_PCM1_FLG_MMAP))) {
				if (new_format)
					new_format &= pcm1->record.hw.formats;
				else
					new_format = format & pcm1->record.hw.formats;
				if (!new_format)	/* not supported format */
					new_format = SND_PCM_FMT_U8;	/* always supported */
			}
			new_mode = snd_pcm1_oss_get_mode(pcm1, flags) & ~SND_PCM1_MODE_TYPE;
			new_mode |= SND_PCM1_MODE_VALID;
			if (new_format & (SND_PCM_FMT_MU_LAW | SND_PCM_FMT_A_LAW | SND_PCM_FMT_U8 |
				SND_PCM_FMT_U16_LE | SND_PCM_FMT_U16_BE))
				new_mode |= SND_PCM1_MODE_U;
			if (new_format & (SND_PCM_FMT_S16_LE | SND_PCM_FMT_S16_BE | SND_PCM_FMT_U16_LE |
			     SND_PCM_FMT_U16_BE | SND_PCM_FMT_IMA_ADPCM))
				new_mode |= SND_PCM1_MODE_16;
			if (new_format & SND_PCM_FMT_MU_LAW)
				new_mode |= SND_PCM1_MODE_ULAW;
			if (new_format & SND_PCM_FMT_A_LAW)
				new_mode |= SND_PCM1_MODE_ALAW;
			if (new_format & SND_PCM_FMT_IMA_ADPCM)
				new_mode |= SND_PCM1_MODE_ADPCM;
		}
#ifdef SND_PCM1_DEBUG_BUFFERS
		printk("new_mode = 0x%x\n", new_mode);
#endif
		if (new_mode != snd_pcm1_oss_get_mode(pcm1, flags)) {
			snd_pcm1_oss_set_format(pcm1, flags, new_format);
			if ((err = snd_pcm1_oss_set_mode(pcm1, flags, new_mode)) < 0)
				return err;
		}
	}
	return snd_pcm1_oss_get_format(pcm1, flags);
}

static int snd_pcm1_oss_rate(snd_pcm1_t * pcm1, unsigned short flags, unsigned int rate)
{
	unsigned int max_rate;

	if (rate > 0) {
		int err;
		max_rate = 48000;
		if (flags & SND_PCM1_LFLG_PLAY) {
			if (max_rate > pcm1->playback.hw.max_rate)
				max_rate = pcm1->playback.hw.max_rate;
		}
		if (flags & SND_PCM1_LFLG_RECORD) {
			if (max_rate > pcm1->record.hw.max_rate)
				max_rate = pcm1->record.hw.max_rate;
		}
		if (rate > max_rate)
			rate = max_rate;
		if ((err = snd_pcm1_oss_set_rate(pcm1, flags, rate)) < 0)
			return err;
	}
	return snd_pcm1_oss_get_rate(pcm1, flags);
}

static int snd_pcm1_oss_set_channels(snd_pcm1_t * pcm1, unsigned short flags,
                                     int channels)
{
	snd_pcm1_channel_t *pchn;
	int err;

#if 0
	printk("set channels = %i\n", channels);
#endif
	if (channels >= 0) {
		if (channels < 1 || channels > 32)
			return -EINVAL;
		if (flags & SND_PCM1_LFLG_PLAY) {
			pchn = &pcm1->playback;
			if (channels > pchn->hw.max_voices)
				channels = pchn->hw.max_voices;
			snd_pcm1_oss_sync_playback(pcm1);
			pchn->voices = channels;
			err = pchn->hw.ioctl(pcm1, SND_PCM1_IOCTL_VOICES, NULL);
			if (err < 0 && err != -ENXIO)
				return err;
			pchn->flags |= SND_PCM1_FLG_BUFFERS;
		}
		if (flags & SND_PCM1_LFLG_RECORD) {
			pchn = &pcm1->record;
			if (channels > pchn->hw.max_voices)
				channels = pchn->hw.max_voices;
			snd_pcm1_oss_sync_record(pcm1);
			pchn->voices = channels;
			err = pchn->hw.ioctl(pcm1, SND_PCM1_IOCTL_VOICES, NULL);
			if (err < 0 && err != -ENXIO)
				return err;
			pchn->flags |= SND_PCM1_FLG_BUFFERS;
		}
	} else {
		if (flags & SND_PCM1_LFLG_PLAY)
			channels = pcm1->playback.voices;
		if (flags & SND_PCM1_LFLG_RECORD)
			channels = pcm1->record.voices;
	}
	return channels;
}

/*
 * This is probably wrong implementation.
 * Now is better way trigger out appropriate channel
 * and reset head, tail & used variables (drain).
 */

static int snd_pcm1_oss_reset(snd_pcm1_t * pcm1, unsigned short flags)
{
	int idx;
	snd_pcm1_channel_t *pchn;

#ifdef SND_PCM1_DEBUG_BUFFERS
	printk("pcm1_reset!!!\n");
#endif
	for (idx = 1; idx <= 2; idx++)
		if (flags & idx) {
			pchn = idx == 1 ? &pcm1->playback : &pcm1->record;
			pchn->flags |= SND_PCM1_FLG_ABORT;	/* drain buffers only */
			if (idx == 1)
				snd_pcm1_oss_sync_playback(pcm1);
			else
				snd_pcm1_oss_sync_record(pcm1);
#if 0
			pchn->requested_block_size =
			    pchn->requested_blocks =
			    pchn->requested_subdivision = 0;
			pchn->processed_bytes = 0;
			pchn->interrupts = 0;
			pchn->flags |= SND_PCM1_FLG_BUFFERS;
#endif
			pchn->flags &= ~SND_PCM1_FLG_ABORT;
		}
	return 0;
}

static int snd_pcm1_oss_get_trigger(snd_pcm1_t * pcm1, unsigned short flags)
{
	unsigned long iflags;
	int result;

	result = 0;
	snd_spin_lock(&pcm1->playback, lock, &iflags);
	if ((pcm1->playback.flags & SND_PCM1_FLG_TRIGGER) &&
	    (flags & SND_PCM1_LFLG_PLAY))
		result |= SND_PCM_ENABLE_PLAYBACK;
	snd_spin_unlock(&pcm1->playback, lock, &iflags);
	snd_spin_lock(&pcm1->record, lock, &iflags);
	if ((pcm1->record.flags & SND_PCM1_FLG_TRIGGER) &&
	    (flags & SND_PCM1_LFLG_RECORD))
		result |= SND_PCM_ENABLE_RECORD;
	snd_spin_unlock(&pcm1->record, lock, &iflags);
#if 0
	printk("get trigger = 0x%x\n", result);
#endif
	return result;
}

static int snd_pcm1_oss_set_trigger(snd_pcm1_t * pcm1, unsigned short flags,
                                    int trigger)
{
	snd_card_t *card;
	snd_pcm1_channel_t *pchn;

#if 0
	printk("set trigger = 0x%x\n", trigger);
#endif
	card = pcm1->card;
	if (flags & SND_PCM1_LFLG_PLAY) {
		pchn = &pcm1->playback;
		if (trigger & SND_PCM_ENABLE_PLAYBACK) {
			pchn->flags |= SND_PCM1_FLG_ENABLE;
			if (!(pchn->flags & SND_PCM1_FLG_TRIGGER)) {
				if (pchn->flags & SND_PCM1_FLG_BUFFERS)
					snd_pcm1_oss_compute_blocks(pcm1, SND_PCM1_PLAYBACK);
				if (pchn->flags & SND_PCM1_FLG_MMAP) {
					if (pchn->hw.flags & SND_PCM1_HW_8BITONLY) {
						if (pchn->mode & SND_PCM1_MODE_16)
							return -ENXIO;
					}
					if (pchn->hw.flags & SND_PCM1_HW_16BITONLY) {
						if (!(pchn->mode & SND_PCM1_MODE_16))
							return -ENXIO;
					}
					if (!(pchn->flags & SND_PCM1_FLG_TRIGGER1)) {	/* first trigger? */
						pchn->hw.prepare(pcm1,
								 pchn->buffer, pchn->used_size,
								 0,	/* assuming that first block is always 0 */
						       pchn->block_size);
					}
					pchn->hw.trigger(pcm1, 1);
					pchn->flags |= SND_PCM1_FLG_TRIGGERA;
				} else {
					snd_pcm1_oss_trigger_playback(pcm1, pchn);
				}
			}
		} else {
			pchn->flags &= ~SND_PCM1_FLG_ENABLE;
			pchn->flags |= SND_PCM1_FLG_ABORT;
			snd_pcm1_oss_sync_playback(pcm1);
			pchn->flags &= ~SND_PCM1_FLG_ABORT;
		}
	}
	if (flags & SND_PCM1_LFLG_RECORD) {
		pchn = &pcm1->record;
		if (trigger & SND_PCM_ENABLE_RECORD) {
			pchn->flags |= SND_PCM1_FLG_ENABLE;
			if (!(pchn->flags & SND_PCM1_FLG_TRIGGER)) {
				if (pchn->flags & SND_PCM1_FLG_BUFFERS)
					snd_pcm1_oss_compute_blocks(pcm1, SND_PCM1_RECORD);
				if (pchn->flags & SND_PCM1_FLG_MMAP) {
					if (pchn->hw.flags & SND_PCM1_HW_8BITONLY) {
						if (pchn->mode & SND_PCM1_MODE_16)
							return -ENXIO;
					}
					if (pchn->hw.flags & SND_PCM1_HW_16BITONLY) {
						if (!(pchn->mode & SND_PCM1_MODE_16))
							return -ENXIO;
					}
					if (!(pchn->flags & SND_PCM1_FLG_TRIGGER1)) {	/* first trigger? */
						pchn->hw.prepare(pcm1,
								 pchn->buffer, pchn->used_size,
								 0,
						       pchn->block_size);
					}
					pchn->hw.trigger(pcm1, 1);
					pchn->flags |= SND_PCM1_FLG_TRIGGERA;
				} else {
					snd_pcm1_oss_trigger_record(pcm1, pchn);
				}
			}
		} else {
			pchn->flags &= ~SND_PCM1_FLG_ENABLE;
			pchn->flags |= SND_PCM1_FLG_ABORT;
			snd_pcm1_oss_sync_playback(pcm1);
			pchn->flags &= ~SND_PCM1_FLG_ABORT;
		}
	}
	return snd_pcm1_oss_get_trigger(pcm1, flags);
}

int snd_pcm1_oss_trigger_sequencer(unsigned int devmask)
{
	int i;
	snd_pcm_t *pcm;
	snd_pcm1_t *pcm1;

	for (i = 0; i < snd_ecards_limit * 2; i++) {
		if ((devmask & (1 << i)) == 0)
			continue;
		pcm = snd_pcm_devices[i >> 1];
		if (!pcm)
			continue;
		pcm1 = (snd_pcm1_t *) pcm->private_data;
		if (!(pcm1->flags & SND_PCM1_LFLG_PLAY))
			continue;
		snd_pcm1_oss_set_trigger(pcm1, SND_PCM1_LFLG_PLAY,
		                               SND_PCM_ENABLE_PLAYBACK);
	}
	return 0;
}

/*
 *  GETOSPACE notes:
 *
 */

static int snd_pcm1_oss_get_space(snd_pcm1_t * pcm1, unsigned short flags,
                                  int direction,
                                  struct snd_pcm_buffer_info *arg)
{
	unsigned long iflags;
	snd_pcm1_channel_t *pchn;
	struct snd_pcm_buffer_info info;

	if (!(flags & (1 << direction)))
		return -EIO;	/* bad file mode */
	if (verify_area(VERIFY_WRITE, arg, sizeof(info)))
		return -EFAULT;
	pchn = direction == SND_PCM1_PLAYBACK ? &pcm1->playback : &pcm1->record;
	if (pchn->flags & SND_PCM1_FLG_BUFFERS)
		snd_pcm1_oss_compute_blocks(pcm1, direction);
	snd_spin_lock(pchn, lock, &iflags);
	info.fragstotal = pchn->blocks;
	info.fragsize = pchn->block_size;
	if (direction == SND_PCM1_PLAYBACK) {
		info.fragments = pchn->blocks - pchn->used;
		if (pchn->frag_size > 0)
			info.fragments--;
	} else {
		info.fragments = pchn->used;
	}
	if (info.fragments < 0)
		info.fragments = 0;
	info.bytes = info.fragments * info.fragsize;
	if (direction == SND_PCM1_PLAYBACK) {
		if (pchn->frag_size > 0)
			info.bytes += pchn->block_size - pchn->frag_size;
	}
#if 0
	if (direction == SND_PCM1_PLAYBACK) {
		if ((pchn->flags & SND_PCM1_FLG_TRIGGER) || pchn->used) {
			info.bytes = pchn->used_size + (pchn->used * pchn->block_size) + pchn->frag_size;
		} else {
			info.bytes = pchn->size;
		}
	} else {
		if ((pchn->flags & SND_PCM1_FLG_TRIGGER) || pchn->used) {
			info.bytes = pchn->used * pchn->block_size;
		} else {
			info.bytes = 0;
		}
	}
#endif
	snd_spin_unlock(pchn, lock, &iflags);
#ifdef SND_PCM1_DEBUG_BUFFERS
	printk("space%i: frags = %i, total = %i, size = %i, bytes = %i (0x%x), frag_size = %i\n", direction, info.fragments, info.fragstotal, info.fragsize, info.bytes, info.bytes, pchn->frag_size);
#endif
	pchn->flags |= SND_PCM1_FLG_ENABLE;
	copy_to_user(arg, &info, sizeof(info));
	return 0;
}

static int snd_pcm1_oss_get_ptr(snd_pcm1_t * pcm1, unsigned short flags,
                                int direction, struct snd_pcm_count_info *arg)
{
	unsigned long iflags;
	snd_pcm1_channel_t *pchn;
	struct snd_pcm_count_info info;
	unsigned int size;

	if (!(flags & (1 << direction)))
		return -EIO;	/* bad file mode */
	if (verify_area(VERIFY_WRITE, arg, sizeof(info)))
		return -EFAULT;
	pchn = direction == SND_PCM1_PLAYBACK ?
					&pcm1->playback : &pcm1->record;
	if (pchn->flags & SND_PCM1_FLG_BUFFERS)
		snd_pcm1_oss_compute_blocks(pcm1, direction);
	snd_spin_lock(pchn, lock, &iflags);
	info.bytes = pchn->processed_bytes;
	if (!(pchn->flags & SND_PCM1_FLG_TRIGGER)) {
		info.ptr = info.bytes == 0 ? pchn->used_size : 0;
	} else {
		if (pchn->hw.flags & SND_PCM1_HW_BLOCKPTR) {
			info.ptr = pchn->hw.pointer(pcm1, pchn->block_size);
			if (info.ptr < 0)
				info.ptr = 0;
			else if (info.ptr >= pchn->block_size)
				info.ptr = pchn->block_size - 1;
			info.ptr += pchn->tail * pchn->block_size;
		} else {
			info.ptr = pchn->hw.pointer(pcm1, pchn->used_size);
			if (info.ptr < 0)
				info.ptr = 0;
			else if (info.ptr >= pchn->used_size)
				info.ptr = pchn->used_size - 1;
		}
		info.ptr &= ~3;	/* align to right value */
	}
	info.blocks = pchn->used;
	if (pchn->flags & SND_PCM1_FLG_MMAP) {
		info.bytes += info.ptr;
		info.blocks = pchn->interrupts;
		pchn->interrupts = 0;
	} else {
		if (!info.bytes) {
			size = pchn->size;
			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;
			}
			info.bytes = info.ptr = size;
		} else {
			info.bytes += info.ptr % pchn->block_size;
		}
		info.blocks = pchn->used;
	}
#if 0
	printk("ptr: flags = 0x%x, bytes = %i, ptr = %i, blocks = %i\n", pchn->flags, info.bytes, info.ptr, info.blocks);
#endif
	snd_spin_unlock(pchn, lock, &iflags);
	copy_to_user(arg, &info, sizeof(info));
	return 0;
}

static int snd_pcm1_oss_get_mapbuf(snd_pcm1_t * pcm1, unsigned short flags,
				   int direction,
				   struct snd_pcm_buffer_description *arg)
{
	snd_pcm1_channel_t *pchn;
	struct snd_pcm_buffer_description info;

	if (!(flags & (1 << direction)))
		return -EIO;	/* bad file mode */
	if (verify_area(VERIFY_WRITE, arg, sizeof(info)))
		return -EFAULT;
	pchn = direction == SND_PCM1_PLAYBACK ?
					&pcm1->playback : &pcm1->record;
	info.buffer = (unsigned char *) pchn->buffer;
	info.size = pchn->size;
#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("mapbuf%i: size = %i\n", direction, info.size);
#endif
	copy_to_user(arg, &info, sizeof(info));
	return 0;
}

static int snd_pcm1_oss_caps(snd_pcm1_t * pcm1, int flags)
{
	unsigned int result;
	snd_pcm_t *pcm;

	pcm = pcm1->pcm;
	result =		/* SND_PCM_CAP_REALTIME | */
	    SND_PCM_CAP_TRIGGER | SND_PCM_CAP_MMAP;
	if (flags & SND_PCM1_LFLG_PLAY) {
		if (pcm1->playback.hw.flags & SND_PCM1_HW_BATCH)
			result |= SND_PCM_CAP_BATCH;
	} else {
		if (pcm1->record.hw.flags & SND_PCM1_HW_BATCH)
			result |= SND_PCM_CAP_BATCH;
	}
	if ((pcm->info_flags & SND_PCM_INFO_DUPLEX) &&
	    flags == SND_PCM1_LFLG_BOTH)
		result |= SND_PCM_CAP_DUPLEX;
	result |= 0x0001;	/* revision - same as SB AWE 64 */
	return result;
}

static int snd_pcm1_oss_nonblock(snd_pcm1_t * pcm1, int flags)
{
	if (flags & SND_PCM1_LFLG_PLAY)
		pcm1->playback.flags |= SND_PCM1_FLG_NONBLK;
	if (flags & SND_PCM1_LFLG_RECORD)
		pcm1->record.flags |= SND_PCM1_FLG_NONBLK;
	return 0;
}

static int snd_pcm1_oss_odelay(snd_pcm1_t * pcm1)
{
	unsigned long flags;
	int ptr, result;

	if (pcm1->playback.flags & SND_PCM1_FLG_MMAP)
		return 0;
	snd_spin_lock(&pcm1->playback, lock, &flags);
	if (pcm1->playback.hw.flags & SND_PCM1_HW_BLOCKPTR) {
		ptr = pcm1->playback.hw.pointer(pcm1, pcm1->playback.block_size);
		if (ptr < 0)
			ptr = 0;
		else if (ptr >= pcm1->playback.block_size)
			ptr = pcm1->playback.block_size - 1;
	} else {
		ptr = pcm1->playback.hw.pointer(pcm1, pcm1->playback.used_size);
		if (ptr < 0)
			ptr = 0;
		else if (ptr >= pcm1->playback.used_size)
			ptr = pcm1->playback.used_size - 1;
		ptr -= pcm1->playback.tail * pcm1->playback.block_size;
		if (ptr < 0)
			ptr = 0;
	}
	ptr &= ~3;		/* align to right value */
	/* compute sum of all waiting bytes to playback */
	result = pcm1->playback.used * pcm1->playback.block_size;
	result += pcm1->playback.frag_size;
	/* decrement current position in fragment */
	result -= ptr;
	snd_spin_unlock(&pcm1->playback, lock, &flags);
	if (result < 0)
		result = 0;
#if 0
	printk("odelay = %i\n", result);
#endif
	return result;
}

static struct snd_stru_pcm1_oss_setup *snd_pcm1_oss_look_for_setup(struct snd_stru_pcm1_oss_setup *setup_list, const char *task_name)
{
	const char *ptr, *ptrl;
	struct snd_stru_pcm1_oss_setup *setup;

	for (setup = setup_list; setup; setup = setup->next) {
		if (!strcmp(setup->task_name, task_name))
			return setup;
	}
	ptr = ptrl = task_name;
	while (*ptr) {
		if (*ptr == '/')
			ptrl = ptr + 1;
		ptr++;
	}
	if (ptrl == task_name)
		return NULL;
	for (setup = setup_list; setup; setup = setup->next) {
		if (!strcmp(setup->task_name, ptrl))
			return setup;
	}
	return NULL;
}

static int snd_pcm1_oss_open_card(snd_pcm1_t * pcm1, short minor,
                                  struct file *file)
{
	unsigned short flags;
	snd_pcm_t *pcm;
	int res;
	char task_name[128];
	struct snd_stru_pcm1_oss_setup *psetup = NULL, *rsetup = NULL;
	int format;

	if (snd_task_name(current, task_name, sizeof(task_name)) < 0)
		return -EFAULT;
	flags = snd_pcm1_file_flags(file);
#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("snd_pcm1_open (1) - pcm = 0x%lx, minor = %i, flags = %i, pcm->flags = 0x%x\n", (long) pcm1, minor, flags, pcm1->flags);
#endif
	pcm = pcm1->pcm;
	if (flags & SND_PCM1_LFLG_PLAY) {
		if (!(pcm->info_flags & SND_PCM_INFO_PLAYBACK))
			return -ENODEV;
		snd_mutex_down(&pcm1->playback, setup_mutex);
		psetup = snd_pcm1_oss_look_for_setup(pcm1->playback.setup_list, task_name);
		snd_mutex_up(&pcm1->playback, setup_mutex);
		if (flags == SND_PCM1_LFLG_BOTH) {
			if (!(pcm->info_flags & SND_PCM_INFO_DUPLEX) ||
			    (psetup && psetup->playback_only)) {
				file->f_flags &= ~O_ACCMODE;
				file->f_flags |= O_WRONLY;
				flags &= ~SND_PCM1_LFLG_RECORD;
			} else {
				snd_mutex_down(&pcm1->record, setup_mutex);
				rsetup = snd_pcm1_oss_look_for_setup(pcm1->record.setup_list, task_name);
				snd_mutex_up(&pcm1->record, setup_mutex);
			}
		}
	} else {
		if (flags & SND_PCM1_LFLG_RECORD) {
			if (!(pcm->info_flags & SND_PCM_INFO_RECORD))
				return -ENODEV;
			snd_mutex_down(&pcm1->record, setup_mutex);
			rsetup = snd_pcm1_oss_look_for_setup(pcm1->record.setup_list, task_name);
			snd_mutex_up(&pcm1->record, setup_mutex);
		}
	}
	if (flags & pcm1->flags)
		return -EBUSY;	/* channel(s) already used */
	if (flags & SND_PCM1_LFLG_PLAY)
		snd_pcm1_clear_channel(&pcm1->playback);
	if (flags & SND_PCM1_LFLG_RECORD)
		snd_pcm1_clear_channel(&pcm1->record);
	pcm1->mask = SND_PCM1_LFLG_BOTH;
	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_oss_interrupt_playback;
		pcm1->playback.setup = psetup;
	}
	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_oss_interrupt_record;
		pcm1->record.setup = rsetup;
	}
	pcm1->flags |= flags | ((flags & 3) << 2);
	snd_pcm1_oss_set_format(pcm1, flags, 0);
	if ((res = snd_pcm1_oss_set_mode(pcm1, flags, 0)) < 0)
		return res;
	switch (minor & SND_MINOR_OSS_MASK) {
	case SND_MINOR_OSS_AUDIO:
	case SND_MINOR_OSS_PCM1:
		format = SND_PCM_FMT_MU_LAW;
		break;
	case SND_MINOR_OSS_PCM_8:
		format = SND_PCM_FMT_U8;
		break;
	case SND_MINOR_OSS_PCM_16:
		format = SND_PCM_FMT_S16_LE;
		break;
	default:
		snd_printd("pcm: bad minor value\n");
		return -EINVAL;
	}
	if ((res = snd_pcm1_oss_format(pcm1, flags, format)) < 0)
		return res;
	if ((res = snd_pcm1_oss_set_rate(pcm1, flags, SND_PCM1_DEFAULT_RATE)) <0)
		return res;
	if ((res = snd_pcm1_oss_set_channels(pcm1, flags, 1)) < 0)
		return res;

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

	return 0;
}

static void snd_pcm1_oss_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_oss_sync_playback(pcm1);	/* synchronize playback */
		pcm1->playback.hw.trigger(pcm1, 0);
		pcm1->playback.hw.close(pcm1);
		pcm1->flags &= ~(SND_PCM1_LFLG_PLAY | SND_PCM1_LFLG_OSS_PLAY);
	}
	if (flags & SND_PCM1_LFLG_RECORD) {
		snd_pcm1_oss_sync_record(pcm1);		/* and record */
		pcm1->record.hw.trigger(pcm1, 0);
		pcm1->record.hw.close(pcm1);
		pcm1->flags &= ~(SND_PCM1_LFLG_RECORD | SND_PCM1_LFLG_OSS_RECORD);
	}
#if 0
	printk("release pcm: done\n");
#endif
}

static int snd_pcm1_oss_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;
	snd_mutex_down(pcm1, open);
	if ((res = snd_pcm1_oss_open_card(pcm1, minor, file)) < 0) {
		snd_mutex_up(pcm1, open);
		return res;
	}
	MOD_INC_USE_COUNT;
	file->private_data = pcm1;
	pcm1->card->use_inc(pcm1->card);
	snd_mutex_up(pcm1, open);
	return 0;
}

static int snd_pcm1_oss_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_oss_close_card(pcm1, file);
		snd_mutex_up(pcm1, open);
		pcm1->card->use_dec(pcm1->card);
		file->private_data = NULL;
	}
	MOD_DEC_USE_COUNT;
	return 0;
}

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

	pcm1 = (snd_pcm1_t *) file->private_data;
	card = pcm1->card;
	if (cmd == SND_OSS_GETVERSION)
		return snd_ioctl_out((long *) arg, SND_OSS_VERSION);
#if 0
	if (((cmd >> 8) & 0xff) == 'M')		/* mixer ioctl - for OSS (grrr) compatibility */
		return snd_mixer_ioctl_card(card, file, cmd, arg);
#endif
	if (((cmd >> 8) & 0xff) != 'P' && ((cmd >> 8) & 0xff) != 'X')
		return -EIO;
	flags = snd_pcm1_file_flags(file) & pcm1->flags & pcm1->mask;
#if 0
	if (cmd != SND_PCM_IOCTL_OSS_GETPBKPTR)
		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_OSS_RESET:
		return snd_pcm1_oss_reset(pcm1, flags);
	case SND_PCM_IOCTL_OSS_SYNC:
		return snd_pcm1_oss_sync(pcm1, flags);
	case SND_PCM_IOCTL_OSS_RATE:
		res = snd_pcm1_oss_rate(pcm1, flags, snd_ioctl_in((long *) arg));
		if (res < 0)
			return res;
		else
			return snd_ioctl_out((long *) arg, res);
	case SND_PCM_IOCTL_OSS_GETRATE:
		return snd_ioctl_out((long *) arg, snd_pcm1_oss_get_rate(pcm1, flags));
	case SND_PCM_IOCTL_OSS_STEREO:
		res = snd_pcm1_oss_set_channels(pcm1, flags, snd_ioctl_in((long *) arg) > 0 ? 2 : 1);
		if (res < 0)
			return res;
		else
			return snd_ioctl_out((long *) arg,  res - 1);
	case SND_PCM_IOCTL_OSS_GETBLKSIZE:
		return snd_ioctl_out((long *) arg, snd_pcm1_oss_get_block_size(pcm1, flags));
	case SND_PCM_IOCTL_OSS_FORMAT:
		res = snd_pcm1_oss_format(pcm1, flags, snd_ioctl_in((long *) arg));
		if (res < 0)
			return res;
		else
			return snd_ioctl_out((long *) arg, res);
	case SND_PCM_IOCTL_OSS_GETFORMAT:
		return snd_ioctl_out((long *) arg, snd_pcm1_oss_get_format(pcm1, flags));
	case SND_PCM_IOCTL_OSS_CHANNELS:
		res = snd_pcm1_oss_set_channels(pcm1, flags, snd_ioctl_in((long *) arg));
		if (res < 0)
			return res;
		else
			return snd_ioctl_out((long *) arg, res);
	case SND_PCM_IOCTL_OSS_GETCHANNELS:
		return snd_ioctl_out((long *) arg, snd_pcm1_oss_set_channels(pcm1, flags, -1));
	case SND_PCM_IOCTL_OSS_FILTER:
	case SND_PCM_IOCTL_OSS_GETFILTER:
		return -EIO;
	case SND_PCM_IOCTL_OSS_POST:	/* wrong implementation */
#if 0
		return snd_pcm1_oss_sync(pcm1, flags);
#else
		return 0;
#endif
	case SND_PCM_IOCTL_OSS_SUBDIVIDE:
		return snd_ioctl_out((long *) arg, snd_pcm1_oss_set_subdivision(pcm1, flags, snd_ioctl_in((long *) arg)));
	case SND_PCM_IOCTL_OSS_SETFRAGMENT:
		return snd_ioctl_out((long *) arg, snd_pcm1_oss_set_fragment(pcm1, flags, snd_ioctl_in((long *) arg)));
	case SND_PCM_IOCTL_OSS_GETFORMATS:
		return snd_ioctl_out((long *) arg, (flags & SND_PCM1_LFLG_PLAY) ? pcm1->playback.hw.formats : pcm1->record.hw.formats);
	case SND_PCM_IOCTL_OSS_GETPBKSPACE:
	case SND_PCM_IOCTL_OSS_GETRECSPACE:
		return snd_pcm1_oss_get_space(pcm1, flags, cmd == SND_PCM_IOCTL_OSS_GETRECSPACE, (struct snd_pcm_buffer_info *) arg);
	case SND_PCM_IOCTL_OSS_NONBLOCK:
		return snd_pcm1_oss_nonblock(pcm1, flags);
	case SND_PCM_IOCTL_OSS_GETCAPS:
		return snd_ioctl_out((long *) arg, snd_pcm1_oss_caps(pcm1, flags));
	case SND_PCM_IOCTL_OSS_GETTRIGGER:
		return snd_ioctl_out((long *) arg, snd_pcm1_oss_get_trigger(pcm1, flags));
	case SND_PCM_IOCTL_OSS_SETTRIGGER:
		res = snd_pcm1_oss_set_trigger(pcm1, flags, snd_ioctl_in((long *) arg));
		if (res < 0)
			return res;
		else
			return snd_ioctl_out((long *) arg, res);
	case SND_PCM_IOCTL_OSS_GETRECPTR:
	case SND_PCM_IOCTL_OSS_GETPBKPTR:
		return snd_pcm1_oss_get_ptr(pcm1, flags, cmd == SND_PCM_IOCTL_OSS_GETRECPTR ? SND_PCM1_RECORD : SND_PCM1_PLAYBACK, (struct snd_pcm_count_info *) arg);
	case SND_PCM_IOCTL_OSS_MAPRECBUFFER:
	case SND_PCM_IOCTL_OSS_MAPPBKBUFFER:
		return snd_pcm1_oss_get_mapbuf(pcm1, flags, cmd == SND_PCM_IOCTL_OSS_MAPRECBUFFER ? SND_PCM1_RECORD : SND_PCM1_PLAYBACK, (struct snd_pcm_buffer_description *) arg);
	case SND_PCM_IOCTL_OSS_SYNCRO:
		/* stop DMA now.. */
		return 0;
	case SND_PCM_IOCTL_OSS_DUPLEX:
		if (snd_pcm1_oss_caps(pcm1, flags) & SND_PCM_CAP_DUPLEX)
			return 0;
		return -EIO;
	case SND_PCM_IOCTL_OSS_GETODELAY:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		return snd_ioctl_out((long *) arg, snd_pcm1_oss_odelay(pcm1));
	case SND_PCM_IOCTL_OSS_PROFILE:
		return 0;	/* silently ignore */
	case SND_PCM_IOCTL_OSS_MASK:
		{
			int val;

			if (snd_pcm1_file_flags(file) != SND_PCM1_LFLG_BOTH)
				return -EIO;
			val = snd_ioctl_in((long *) arg);
			val &= SND_PCM1_LFLG_BOTH;
			if (!val)
				return -EINVAL;
			pcm1->mask = val;
			return 0;
		}
#ifdef CONFIG_SND_DEBUG
	default:
		snd_printk("pcm: unknown command = 0x%x\n", cmd);
#endif
	}
	return -EIO;
}

static long snd_pcm1_oss_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_oss_buffer_to_user(pcm1, buf, count);
}

static long snd_pcm1_oss_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_oss_user_to_buffer(pcm1, buf, count);
}

#ifdef SND_POLL
static unsigned int snd_pcm1_oss_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;

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

	record = &pcm1->record;
	if ((fflags & SND_PCM1_LFLG_RECORD) &&
	    (record->flags & SND_PCM1_FLG_BUFFERS))
		snd_pcm1_oss_compute_blocks(pcm1, SND_PCM1_RECORD);
	playback = &pcm1->playback;
	if ((fflags & SND_PCM1_LFLG_PLAY) &&
	    (playback->flags & SND_PCM1_FLG_BUFFERS))
		snd_pcm1_oss_compute_blocks(pcm1, SND_PCM1_PLAYBACK);

	if (fflags & SND_PCM1_LFLG_RECORD) {
		snd_pcm1_oss_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(playback, sleep_lock, &flags);
		snd_getlock(playback, sleep) |= SND_WK_SLEEP;
		snd_poll_wait(file, playback, sleep, wait);
		snd_spin_unlock(playback, sleep_lock, &flags);
	}
	mask = 0;
	if (fflags & SND_PCM1_LFLG_RECORD) {
		if (record->flags & SND_PCM1_FLG_MMAP) {
			if (record->interrupts)
				mask |= POLLIN | POLLRDNORM;
		} else {
			if (record->used)
				mask |= POLLIN | POLLRDNORM;
		}
	}
	if (fflags & SND_PCM1_LFLG_PLAY) {
		if (playback->flags & SND_PCM1_FLG_MMAP) {
			if (playback->interrupts)
				mask |= POLLOUT | POLLWRNORM;
		} else {
			if (playback->used < playback->blocks)
				mask |= POLLOUT | POLLWRNORM;
		}
	}
	return mask;
}
#else
static int snd_pcm1_oss_select(struct file *file, int sel_type,
                               select_table * wait)
{
	int ok;
	snd_pcm1_t *pcm1;
	unsigned long flags;
	snd_pcm1_channel_t *pchn;
	unsigned short fflags;

	pcm1 = (snd_pcm1_t *) file->private_data;
	fflags = snd_pcm1_file_flags(file) & pcm1->flags;
	if (!pcm1)
		return -EIO;
	switch (sel_type) {
	case SEL_IN:
		if (!(fflags & SND_PCM1_LFLG_RECORD))
			return 0;
		pchn = &pcm1->record;
		if (pchn->flags & SND_PCM1_FLG_BUFFERS)
			snd_pcm1_oss_compute_blocks(pcm1, SND_PCM1_RECORD);
		snd_pcm1_oss_trigger_record(pcm1, pchn);
		snd_spin_lock(pchn, sleep_lock, &flags);
		ok = pchn->flags & SND_PCM1_FLG_MMAP ?
				!pchn->interrupts : !pchn->used;
		if (ok) {
			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_OUT:
		if (!(fflags & SND_PCM1_LFLG_PLAY))
			return 0;
		pchn = &pcm1->playback;
		if (pchn->flags & SND_PCM1_FLG_BUFFERS)
			snd_pcm1_oss_compute_blocks(pcm1, SND_PCM1_PLAYBACK);
		snd_spin_lock(pchn, sleep_lock, &flags);
		ok = pchn->flags & SND_PCM1_FLG_MMAP ?
				 !pchn->interrupts : pchn->used >= pchn->blocks;
		if (ok) {
			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

static void snd_pcm1_oss_vma_open(struct vm_area_struct *area)
{
	MOD_INC_USE_COUNT;
}

static void snd_pcm1_oss_vma_close(struct vm_area_struct *area)
{
	snd_dma_notify_vma_close(area);
	MOD_DEC_USE_COUNT;
}

static struct vm_operations_struct snd_pcm1_oss_vm_ops =
{
	snd_pcm1_oss_vma_open,	/* open */
	snd_pcm1_oss_vma_close,	/* close */
	NULL,			/* unmap */
	NULL,			/* protect */
	NULL,			/* sync */
	NULL,			/* advise */
	NULL,			/* nopage */
	NULL,			/* wppage */
	NULL,			/* swapout */
	NULL,			/* swapin */
};

static int snd_pcm1_oss_mmap(struct inode *inode, struct file *file,
                             struct vm_area_struct *vma)
{
	snd_pcm_t *pcm;
	snd_pcm1_t *pcm1;
	unsigned short flags;
	snd_pcm1_channel_t *pchn;
	unsigned long size;
	struct snd_stru_dma *pdma;

	pcm1 = (snd_pcm1_t *) file->private_data;
	pcm = pcm1->pcm;
	if (!(pcm->info_flags & SND_PCM_INFO_MMAP))
		return -EIO;
	flags = snd_pcm1_file_flags(file) & pcm1->flags;
	if ((vma->vm_flags & (VM_READ | VM_WRITE)) == (VM_READ | VM_WRITE))
		return -EINVAL;
	if (vma->vm_flags & VM_READ) {
		if (!(flags & SND_PCM1_LFLG_RECORD))
			return -EINVAL;
		pchn = &pcm1->record;
	} else if (vma->vm_flags & VM_WRITE) {
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EINVAL;
		pchn = &pcm1->playback;
	} else
		return -EINVAL;
	pdma = pchn->dmaptr;
	if (!pdma)
		return -ENOENT;
	if (vma->vm_offset != 0)
		return -EINVAL;
	size = vma->vm_end - vma->vm_start;
	if (size != pchn->size && size != pchn->used_size) {
		snd_printk("snd-pcm: wrong mmap() size 0x%x, should be 0x%x\n", (unsigned int) size, pchn->size);
	}
	pchn->mmap_size = size;
#if 0
	printk("remap: to=0x%x from=0x%x size=0x%x\n", (int) vma->vm_start, (int) virt_to_bus(pchn->buffer), (int) size);
#endif
	if (remap_page_range(vma->vm_start,
			     virt_to_bus(pchn->buffer),
			     size,
			     vma->vm_page_prot))
		return -EAGAIN;
	if (vma->vm_ops)
		return -EINVAL;	/* Hmm... shouldn't happen */
#ifndef LINUX_2_1
	vma->vm_inode = inode;
	inode->i_count++;
#else
	vma->vm_file = file;
#if LinuxVersionCode(2, 2, 3) > LINUX_VERSION_CODE
	file->f_count++;
#endif
#endif
	vma->vm_ops = &snd_pcm1_oss_vm_ops;
	MOD_INC_USE_COUNT;
	pdma->mmaped = 1;
	pdma->vma = vma;
	pchn->flags |= SND_PCM1_FLG_MMAP;
	snd_pcm1_fill_with_neutral(pcm1, pchn);
#ifdef SND_PCM1_DEBUG_BUFFERS
	printk("mmap ok..\n");
#endif
	return 0;
}

/*
 *  /proc interface
 */

static void snd_pcm1_oss_proc_read1(snd_info_buffer_t * buffer,
				    snd_pcm1_t * pcm1,
                                    struct snd_stru_pcm1_oss_setup *setup,
                                    const char *direction)
{
	while (setup) {
		snd_iprintf(buffer, "%s %s %u %u%s\n",
			    direction,
			    setup->task_name,
			    setup->fragments,
			    setup->fragment_size,
			    setup->playback_only ? " WR_ONLY" : "");
		setup = setup->next;
	}
}

static void snd_pcm1_oss_proc_read(snd_info_buffer_t * buffer,
				   void *private_data)
{
	snd_pcm1_t *pcm1 = (snd_pcm1_t *) private_data;

	snd_mutex_down(&pcm1->playback, setup_mutex);
	snd_pcm1_oss_proc_read1(buffer, pcm1, pcm1->playback.setup_list, "Playback");
	snd_mutex_up(&pcm1->playback, setup_mutex);
	snd_mutex_down(&pcm1->record, setup_mutex);
	snd_pcm1_oss_proc_read1(buffer, pcm1, pcm1->record.setup_list, "Record");
	snd_mutex_up(&pcm1->record, setup_mutex);
}

static void snd_pcm1_oss_proc_free_setup_list(snd_pcm1_channel_t * pchn)
{
	struct snd_stru_pcm1_oss_setup *setup, *setupn;

	for (setup = pchn->setup_list; setup; setup = setupn) {
		setupn = setup->next;
		snd_free_str(setup->task_name);
		snd_free(setup, sizeof(struct snd_stru_pcm1_oss_setup));
	}
	pchn->setup_list = NULL;
	pchn->setup = NULL;
}

static void snd_pcm1_oss_proc_write(snd_info_buffer_t * buffer,
                                    void *private_data)
{
	snd_pcm1_t *pcm1 = (snd_pcm1_t *) private_data;
	snd_pcm1_channel_t *pchn;
	char line[512], str[32], task_name[512], *ptr;
	int idx, idx1;
	struct snd_stru_pcm1_oss_setup *setup, *setup1, template;

	while (!snd_info_get_line(buffer, line, sizeof(line))) {
		pchn = NULL;
		if (!strncmp(line, "Playback ", 9)) {
			pchn = &pcm1->playback;
			idx = 9;
		} else if (!strncmp(line, "Record ", 8)) {
			pchn = &pcm1->record;
			idx = 8;
		} else {
			buffer->error = -EINVAL;
			continue;
		}
		snd_mutex_down(pchn, setup_mutex);
		memset(&template, 0, sizeof(template));
		ptr = snd_info_get_str(task_name, line + idx, sizeof(task_name));
		if (!strcmp(task_name, "clear") || !strcmp(task_name, "erase")) {
			snd_pcm1_oss_proc_free_setup_list(pchn);
			snd_mutex_up(pchn, setup_mutex);
			continue;
		}
		for (setup = pchn->setup_list; setup; setup = setup->next) {
			if (!strcmp(setup->task_name, task_name)) {
				memcpy(&template, setup, sizeof(template));
				break;
			}
		}
		ptr = snd_info_get_str(str, ptr, sizeof(str));
		template.fragments = simple_strtoul(str, NULL, 10);
		ptr = snd_info_get_str(str, ptr, sizeof(str));
		template.fragment_size = simple_strtoul(str, NULL, 10);
		for (idx1 = 31; idx1 >= 0; idx1--)
			if (template.fragment_size & (1 << idx1))
				break;
		for (idx1--; idx1 >= 0; idx1--)
			template.fragment_size &= ~(1 << idx1);
		do {
			ptr = snd_info_get_str(str, ptr, sizeof(str));
			if (!strcmp(str, "WR_ONLY"))
				template.playback_only = 1;
		} while (*str);
		if (!setup) {
			setup = (struct snd_stru_pcm1_oss_setup *) snd_malloc(sizeof(struct snd_stru_pcm1_oss_setup));
			if (setup) {
				if (!pchn->setup_list) {
					pchn->setup_list = setup;
				} else {
					for (setup1 = pchn->setup_list; setup1->next; setup1 = setup1->next);
					setup1->next = setup;
				}
				template.task_name = snd_malloc_strdup(task_name);
			} else {
				buffer->error = -ENOMEM;
			}
		}
		if (setup)
			memcpy(setup, &template, sizeof(template));
		snd_mutex_up(pchn, setup_mutex);
	}
}

static void snd_pcm1_oss_proc_init(snd_pcm_t * pcm)
{
	snd_info_entry_t *entry;
	snd_pcm1_t *pcm1 = (snd_pcm1_t *) pcm->private_data;
	char name[16];

	sprintf(name, "pcm%io", pcm->device);
	if ((entry = snd_info_create_entry(pcm->card, name)) != NULL) {
		entry->private_data = pcm->private_data;
		entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
		entry->t.text.read_size = 8192;
		entry->t.text.read = snd_pcm1_oss_proc_read;
		entry->t.text.write_size = 8192;
		entry->t.text.write = snd_pcm1_oss_proc_write;
		if (snd_info_register(entry) < 0) {
			snd_info_free_entry(entry);
			entry = NULL;
		}
		snd_info_restore_text(entry);
	}
	pcm1->proc_oss_entry = entry;
}

static void snd_pcm1_oss_proc_done(snd_pcm_t * pcm)
{
	snd_pcm1_t *pcm1 = (snd_pcm1_t *) pcm->private_data;

	if (pcm1->proc_oss_entry) {
		snd_info_store_text(pcm1->proc_oss_entry);
		snd_info_unregister(pcm1->proc_oss_entry);
		pcm1->proc_oss_entry = NULL;
		snd_pcm1_oss_proc_free_setup_list(&pcm1->playback);
		snd_pcm1_oss_proc_free_setup_list(&pcm1->record);
	}
}

/*
 *  ENTRY functions
 */

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

	NULL,			/* unregister */

	NULL,			/* lseek */
	snd_pcm1_oss_read,	/* read */
	snd_pcm1_oss_write,	/* write */
	snd_pcm1_oss_open,	/* open */
	snd_pcm1_oss_release,	/* release */
#ifdef SND_POLL
	snd_pcm1_oss_poll,	/* poll */
#else
	snd_pcm1_oss_select,	/* select */
#endif
	snd_pcm1_oss_ioctl,	/* ioctl */
	snd_pcm1_oss_mmap,	/* mmap */
};

static int snd_pcm1_oss_register_minor(unsigned short native_minor,
				       snd_pcm_t * pcm)
{
	char name[32];

	pcm->ossreg = 0;
	if (pcm->device < 2) {
		sprintf(name, "dsp%i%i", pcm->card->number, pcm->device);
		if (snd_register_oss_device(SND_OSS_DEVICE_TYPE_PCM,
				pcm->card, pcm->device, &snd_pcm1_oss_reg,
				name) < 0) {
			snd_printk("unable to register OSS PCM device %i:%i\n", pcm->card->number, pcm->device);
		} else {
			snd_oss_info_register(SND_OSS_INFO_DEV_AUDIO,
					      pcm->device,
					      pcm->name);
			pcm->ossreg = 1;
			snd_pcm1_oss_proc_init(pcm);
		}
	}
	return 0;
}

static int snd_pcm1_oss_unregister_minor(unsigned short native_minor,
				         snd_pcm_t * pcm)
{
	if (pcm->ossreg) {
		snd_unregister_oss_device(SND_OSS_DEVICE_TYPE_PCM,
					pcm->card, pcm->device);
		snd_oss_info_unregister(SND_OSS_INFO_DEV_AUDIO, pcm->device);
		pcm->ossreg = 0;
		snd_pcm1_oss_proc_done(pcm);
	}
	return 0;
}

static struct snd_stru_pcm_notify snd_pcm1_oss_notify =
{
	snd_pcm1_oss_register_minor,
	snd_pcm1_oss_unregister_minor,
	NULL
};

int init_module(void)
{
	int err;

#ifndef LINUX_2_1
	register_symtab(NULL);	/* this modules doesn't exports anything */
#endif
	if ((err = snd_pcm_notify(&snd_pcm1_oss_notify, 0)) < 0)
		return err;
	return 0;
}

void cleanup_module(void)
{
	snd_pcm_notify(&snd_pcm1_oss_notify, 1);
}
