/*
 *  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.
 * Wed Aug 18 16:11:44 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 again.
 */

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

EXPORT_NO_SYMBOLS;

#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_pcm_subchn_t *subchn);
static int snd_pcm1_oss_nonblock(snd_pcm_file_t * pcm_file);

/*
 *  interrupt callbacks from lowlevel driver
 */

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

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	if (subchn1 == NULL)
		return;
	pchn1 = (snd_pcm1_channel_t *) subchn->pcm->playback.private_data;
	if (pchn1 == NULL)
		return;
	if (!(subchn1->flags & SND_PCM1_FLG_TRIGGER)) {
		snd_printd("snd_pcm1_oss_interrupt_playback: Oops, playback interrupt when playback isn't active!!!\n");
		return;
	}
	if (!(subchn1->flags & SND_PCM1_FLG_MMAP)) {
		/* try clear first few bytes from non-active block */
		if (pchn1->hw.flags & SND_PCM1_HW_AUTODMA)
			pchn1->hw.dma_neutral(pchn1->private_data,
					      subchn, subchn1->buffer,
					      subchn1->tail * subchn1->block_size,
					      16, subchn1->neutral_byte);
		if (subchn1->used == 1) {
			if (!(subchn1->flags & SND_PCM1_FLG_SYNC)) {
#ifdef SND_PCM1_DEBUG_BUFFERS
				printk("playback: underrun!!! - jiffies = %li\n", jiffies);
#endif
				subchn1->total_xruns++;
				subchn1->xruns++;
			}
		}
		spin_lock_irqsave(&subchn1->lock, flags);
		subchn1->interrupts++;
		if (subchn1->used > 0) {
			subchn1->processed_bytes += subchn1->block_size;
			subchn1->used--;
		}
		subchn1->tail++;
		subchn1->tail %= subchn1->blocks;
		if (subchn1->used > 0)
			trigger = 1;
		spin_unlock_irqrestore(&subchn1->lock, flags);
	} else {
		spin_lock_irqsave(&subchn1->lock, flags);
		subchn1->interrupts++;
		subchn1->processed_bytes += subchn1->block_size;
		subchn1->tail++;
		subchn1->tail %= subchn1->blocks;
		spin_unlock_irqrestore(&subchn1->lock, flags);
		trigger = 1;
	}
	if (trigger && !(pchn1->hw.flags & SND_PCM1_HW_AUTODMA)) {
		pchn1->hw.prepare(pchn1->private_data, subchn,
				  subchn1->buffer, subchn1->used_size,
				  subchn1->tail * subchn1->block_size,
				  subchn1->block_size);
		pchn1->hw.trigger(pchn1->private_data, subchn, 1);
	}
	if (!trigger) {
		if (pchn1->hw.flags & SND_PCM1_HW_AUTODMA) {
			pchn1->hw.trigger(pchn1->private_data, subchn, 0);
		}
		subchn1->flags &= ~SND_PCM1_FLG_TRIGGER;
	}
	wake_up(&subchn1->sleep);
}

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

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	if (subchn1 == NULL)
		return;
	pchn1 = (snd_pcm1_channel_t *) subchn->pcm->capture.private_data;
	if (pchn1 == NULL)
		return;
	if (!(subchn1->flags & SND_PCM1_FLG_TRIGGER)) {
		snd_printd("snd_pcm1_oss_interrupt_capture: Oops, capture interrupt when capture isn't active!!!\n");
		return;
	}
	trigger = 0;
	spin_lock_irqsave(&subchn1->lock, flags);
	if (!(subchn1->flags & SND_PCM1_FLG_SYNC))
		trigger = 1;
	if (!(subchn1->flags & SND_PCM1_FLG_MMAP)) {
		if (subchn1->used < subchn1->blocks) {
			subchn1->used++;
		} else {
			subchn1->total_xruns++;
			subchn1->xruns++;
			trigger = 0;
		}
		subchn1->interrupts++;
		subchn1->processed_bytes += subchn1->block_size;
		subchn1->head++;
		subchn1->head %= subchn1->blocks;
	} else {
		subchn1->interrupts++;
		subchn1->processed_bytes += subchn1->block_size;
		subchn1->head++;
		subchn1->head %= subchn1->blocks;
	}
	spin_unlock_irqrestore(&subchn1->lock, flags);
	if (trigger && !(pchn1->hw.flags & SND_PCM1_HW_AUTODMA)) {
		pchn1->hw.prepare(pchn1->private_data,
				  subchn,
				  subchn1->buffer, subchn1->used_size,
				  subchn1->head * subchn1->block_size,
				  subchn1->block_size);
		pchn1->hw.trigger(pchn1->private_data, subchn, 1);
	}
	if (!trigger) {
		if (pchn1->hw.flags & SND_PCM1_HW_AUTODMA) {
			pchn1->hw.trigger(pchn1->private_data, subchn, 0);
		}
		subchn1->flags &= ~SND_PCM1_FLG_TRIGGER;
	}
	wake_up(&subchn1->sleep);
}

/*
 *  trigger standard buffered playback
 */

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

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

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

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

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

/*
 *  user to dma
 */

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

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

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

#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 &= ~((subchn1->voices * (subchn1->mode & SND_PCM1_MODE_16 ? 2 : 1)) - 1);
	subchn1->flags &= ~SND_PCM1_FLG_ABORT;
	if (subchn1->flags & SND_PCM1_FLG_MMAP)
		return -EIO;	/* go to hell... */
	if (subchn1->flags & SND_PCM1_FLG_BUFFERS)
		snd_pcm1_oss_compute_blocks(subchn);
	if (subchn1->flags & SND_PCM1_FLG_NEUTRAL)
		snd_pcm1_fill_with_neutral(subchn);

	result = 0;

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

		if (copy_from_user(buffer, buf, 8))
			return -EFAULT;
		if (!memcmp(buffer, ".snd", 4)) {
			tmp = (buffer[4] << 24) | (buffer[5] << 16) |
			      (buffer[6] << 8) | (buffer[7]);
			if (tmp > count)
				tmp = count;
			if (tmp < 128) {
				buf += tmp;
				count -= tmp;
				result += tmp;
			}
		}
	}
	while (count > 0) {
		while (subchn1->used >= subchn1->blocks) {
			if (!(subchn1->flags & SND_PCM1_FLG_TRIGGER))
				snd_pcm1_oss_trigger_playback(subchn);
			if (subchn1->flags & SND_PCM1_FLG_NONBLK)
				return result;
			if (!(subchn1->flags & SND_PCM1_FLG_ENABLE))
				return result;
			timeout = interruptible_sleep_on_timeout(&subchn1->sleep, 10 * HZ);
			if (signal_pending(current)) {
				subchn1->flags |= SND_PCM1_FLG_ABORT;
				return result;
			}
			if (subchn1->used >= subchn1->blocks && !timeout) {
				snd_printd("pcm1_user_to_dma: timeout, new block discarded\n");
				return -EIO;
			}
		}

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

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

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

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

		spin_lock_irqsave(&subchn1->lock, flags);
		subchn1->frag_size += tmp;
		if (subchn1->lastxruns != subchn1->xruns)
			snd_pcm1_oss_playback_underrun(subchn);
		if (subchn1->frag_size >= subchn1->block_size) {
			subchn1->used++;
			subchn1->head++;
			subchn1->head %= subchn1->blocks;
			subchn1->frag_size = 0;
		}
		spin_unlock_irqrestore(&subchn1->lock, flags);
	}
	/* hmm. I'm not sure that this is same as in OSS */
	/* if bellow two lines are removed - RealVideo works much better */
	if (subchn1->used > 0)
		snd_pcm1_oss_trigger_playback(subchn);
	return result;
}

/*
 *  trigger standard buffered capture
 */

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

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

/*
 *  dma to user
 */

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

	if (count <= 0)
		return 0;

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

	count &= ~((subchn1->voices * (subchn1->mode & SND_PCM1_MODE_16 ? 2 : 1)) - 1);
	subchn1->flags &= ~SND_PCM1_FLG_ABORT;
	if (subchn1->flags & SND_PCM1_FLG_MMAP)
		return -EIO;
	if (subchn1->flags & SND_PCM1_FLG_BUFFERS)
		snd_pcm1_oss_compute_blocks(subchn);
	if (subchn1->flags & SND_PCM1_FLG_NEUTRAL)
		snd_pcm1_fill_with_neutral(subchn);

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

	while (count > 0) {
		while (!subchn1->used) {
			snd_pcm1_oss_trigger_capture(subchn);
			if (subchn1->flags & SND_PCM1_FLG_NONBLK)
				return result;
			timeout = interruptible_sleep_on_timeout(&subchn1->sleep, 10 * HZ);
			if (signal_pending(current)) {
				subchn1->flags |= SND_PCM1_FLG_ABORT;
				return -EINTR;
			}
			if (!subchn1->used && !timeout) {
				snd_printd("snd_pcm1_dma_to_user: data timeout\n");
				return -EIO;
			}
		}

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

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

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

	return result;
}

/*
 *  synchronize playback
 */

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

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

	if (subchn1->flags & SND_PCM1_FLG_ABORT) {
		if (subchn1->flags & SND_PCM1_FLG_TRIGGER) {
			pchn1->hw.trigger(pchn1->private_data, subchn, 0);
			subchn1->flags &= ~SND_PCM1_FLG_TRIGGER;
		}
		goto __end_done;
	}
	spin_lock_irqsave(&subchn1->lock, flags);
	subchn1->flags |= SND_PCM1_FLG_SYNC;
	if (subchn1->used < subchn1->blocks && subchn1->frag_size > 0) {
		unsigned int size, count, offset;

		if (subchn1->lastxruns != subchn1->xruns)
			snd_pcm1_oss_playback_underrun(subchn);
		size = subchn1->frag_size;
		count = subchn1->block_size - size;
		offset = offset = (subchn1->head * subchn1->block_size) + size;
		subchn1->used++;
		subchn1->head++;
		subchn1->head %= subchn1->blocks;
		spin_unlock_irqrestore(&subchn1->lock, flags);
		if (count > 0)
			pchn1->hw.dma_neutral(pchn1->private_data, subchn,
					      subchn1->buffer, offset, count,
					      subchn1->neutral_byte);
	} else {
		spin_unlock_irqrestore(&subchn1->lock, flags);
	}
	if (!subchn1->used)
		goto __end;	/* probably mmaped access */

	snd_pcm1_oss_trigger_playback(subchn);

	while (subchn1->used) {
		timeout = interruptible_sleep_on_timeout(&subchn1->sleep, 10 * HZ);
		if (signal_pending(current)) {
			subchn1->flags |= SND_PCM1_FLG_ABORT;
			pchn1->hw.trigger(pchn1->private_data, subchn, 0);
			subchn1->flags &= ~SND_PCM1_FLG_TRIGGER;
			goto __end_done;
		}
		if (subchn1->used && !timeout) {
			snd_printd("snd_pcm1_oss_sync_playback: timeout, skipping waiting blocks\n");
			return;
		}
	}

      __end_done:
	subchn1->flags |= SND_PCM1_FLG_NEUTRAL;
	snd_pcm1_fill_with_neutral(subchn);
	subchn1->flags &= ~SND_PCM1_FLG_TRIGGER1;
	subchn1->head = subchn1->tail = subchn1->used = 0;
	subchn1->frag_size = 0;
      __end:
	subchn1->flags &= ~SND_PCM1_FLG_SYNC;
}

static void snd_pcm1_oss_sync_capture(snd_pcm_subchn_t * subchn)
{
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	long timeout;

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

	subchn1->flags |= SND_PCM1_FLG_SYNC;
	while (subchn1->flags & SND_PCM1_FLG_TRIGGER) {	/* still captureing? */
		timeout = interruptible_sleep_on_timeout(&subchn1->sleep, 10 * HZ);
		if (signal_pending(current)) {
			subchn1->flags |= SND_PCM1_FLG_ABORT;
			pchn1->hw.trigger(pchn1->private_data, subchn, 0);
			subchn1->flags &= ~(SND_PCM1_FLG_SYNC | SND_PCM1_FLG_TRIGGER);
			return;
		}
		if ((subchn1->flags & SND_PCM1_FLG_TRIGGER) && !timeout) {
			snd_printd("snd_pcm1_sync_capture: sync timeout\n");
			subchn1->flags &= ~SND_PCM1_FLG_SYNC;
			break;
		}
	}
	subchn1->flags &= ~SND_PCM1_FLG_SYNC;
	snd_pcm1_fill_with_neutral(subchn);
	subchn1->flags &= ~SND_PCM1_FLG_TRIGGER1;
	subchn1->head = subchn1->tail = subchn1->used = 0;
	subchn1->frag_size = subchn1->block_size;
}

static int snd_pcm1_oss_sync(snd_pcm_file_t * pcm_file)
{
	if (pcm_file->playback)
		snd_pcm1_oss_sync_playback(pcm_file->playback);
	if (pcm_file->capture)
		snd_pcm1_oss_sync_capture(pcm_file->capture);
	return 0;
}

/*
 *  other things
 */

static void snd_pcm1_oss_compute_blocks(snd_pcm_subchn_t * subchn)
{
	unsigned int block, blocks, bps, min;
	unsigned long flags;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;

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

	if (!subchn->capture) {
		snd_pcm1_oss_sync_playback(subchn);
	} else {
		snd_pcm1_oss_sync_capture(subchn);
	}
	down(&pchn1->setup_mutex);
	spin_lock_irqsave(&subchn1->lock, flags);
	subchn1->used_size = subchn1->mmap_size > 0 ?
				subchn1->mmap_size : subchn1->size;
	if (pchn1->hw.flags & SND_PCM1_HW_8BITONLY) {
		if (subchn1->mode & SND_PCM1_MODE_16)
			subchn1->used_size <<= 1;
	}
	if (pchn1->hw.flags & SND_PCM1_HW_16BITONLY) {
		if (!(subchn1->mode & SND_PCM1_MODE_16))
			subchn1->used_size >>= 1;
	}
#ifdef SND_PCM1_DEBUG_BUFFERS
	printk("pchn -> used_size = %i, size = %i, mode = 0x%x\n",
				subchn1->used_size, subchn1->size, subchn1->mode);
#endif
	for (min = 1, bps = pchn1->hw.min_fragment; bps > 1; bps--)
		min <<= 1;
	/* load another values, if user need it */
	if (pchn1->setup) {
		if (pchn1->setup->fragment_size)
			subchn1->requested_block_size = pchn1->setup->fragment_size;
		if (pchn1->setup->fragments)
			subchn1->requested_blocks = pchn1->setup->fragments;
	}
	if (subchn1->requested_block_size > 0) {
		block = subchn1->requested_block_size;
		block &= ~(pchn1->hw.align | 3);
		if (block < min)
			block = min;
		pchn1->hw.ioctl(pchn1->private_data, subchn,
				SND_PCM1_IOCTL_FRAG, (unsigned long *)&block);
		blocks = subchn1->used_size / block;
		if (blocks > subchn1->requested_blocks)
			blocks = subchn1->requested_blocks;
	} else {
		bps = subchn1->rate * subchn1->voices;
		if (subchn1->mode & SND_PCM1_MODE_16)
			bps <<= 1;
		if (subchn1->mode & SND_PCM1_MODE_ADPCM)
			bps >>= 2;
		bps >>= 2;	/* make only fragment for 0.25 sec */
		if (bps <= 0)
			bps = 16;
		block = subchn1->used_size;
		while (block > bps)
			block >>= 1;
		if (block == subchn1->used_size)
			block >>= 1;
#if 0
		if (capture)
			block /= 4;	/* small fragment when captureing */
#endif
		block &= ~(pchn1->hw.align | 3);	/* align to 4 */
		if (block < min)
			block = min;
		pchn1->hw.ioctl(pchn1->private_data, subchn,
				SND_PCM1_IOCTL_FRAG, (unsigned long *)&block);
		blocks = subchn1->used_size / block;
	}
	if (blocks < 2) {
		blocks = 2;
		if (block > subchn1->used_size >> 1)
			block = subchn1->used_size >> 1;
	}
	if (blocks > 128)
		blocks = 128;
	subchn1->used_size = blocks * block;
	subchn1->blocks = blocks;
	subchn1->block_size = block;
	subchn1->flags |= SND_PCM1_FLG_NEUTRAL;
	subchn1->flags &= ~SND_PCM1_FLG_BUFFERS;
	if (subchn->capture) {
		subchn1->frag_size = subchn1->block_size;
	}
	spin_unlock_irqrestore(&subchn1->lock, flags);
	up(&pchn1->setup_mutex);
	snd_pcm1_proc_format(subchn);
#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("used_size = %i, blocks = %i, blocks_size = %i, mmap = %s\n",
		subchn1->used_size, subchn1->blocks, subchn1->block_size,
		subchn1->flags & SND_PCM1_FLG_MMAP ? "yes" : "no");
#endif
}

static void snd_pcm1_oss_set_subdivision1(snd_pcm_subchn_t *subchn,
					  unsigned int subdivision)
{
	snd_pcm1_subchn_t *subchn1;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	if (subchn1->requested_subdivision != subdivision) {
		if (!subchn->capture)
			snd_pcm1_oss_sync_playback(subchn);
		else
			snd_pcm1_oss_sync_capture(subchn);
		subchn1->requested_subdivision = subdivision;
		subchn1->flags |= SND_PCM1_FLG_NEUTRAL |
				  SND_PCM1_FLG_BUFFERS;
	}
}

static int snd_pcm1_oss_set_subdivision(snd_pcm_file_t * pcm_file,
                                        unsigned int subdivision)
{
	if (pcm_file->playback != NULL)
		snd_pcm1_oss_set_subdivision1(pcm_file->playback, subdivision);
	if (pcm_file->capture != NULL)
		snd_pcm1_oss_set_subdivision1(pcm_file->capture, subdivision);
	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 void snd_pcm1_oss_set_fragment1(snd_pcm_subchn_t *subchn,
				       int size, int count)
{
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	int bytes;

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

	if (pchn1->setup) {
		count = pchn1->setup->fragments;
		bytes = 0;
		size = pchn1->setup->fragment_size;
		while (size > 1) {
			bytes++;
			size >>= 1;
		}
	}
	if (subchn1->requested_block_size != size ||
	    subchn1->requested_blocks != count) {
		if (!subchn->capture)
			snd_pcm1_oss_sync_playback(subchn);
		else
			snd_pcm1_oss_sync_capture(subchn);
		subchn1->requested_block_size = size;
		subchn1->requested_blocks = count;
		subchn1->flags |= SND_PCM1_FLG_NEUTRAL |
				  SND_PCM1_FLG_BUFFERS;
	}
}

static int snd_pcm1_oss_set_fragment(snd_pcm_file_t * pcm_file,
				     unsigned int fragment)
{
	int size, size1, bytes, count, min_fragment, tmp;

	size = 0x10000;
	count = (fragment >> 16) & 0xffff;
	bytes = fragment & 0xffff;
	if (!bytes) {
		if (pcm_file->playback)
			size = ((snd_pcm1_subchn_t *) pcm_file->playback->private_data)->requested_block_size;
		if (pcm_file->capture)
			size = ((snd_pcm1_subchn_t *) pcm_file->capture->private_data)->requested_block_size;
		if (!size)
			size = 0x10000;
		size1 = size;
		while (size1 > 0) {
			bytes++;
			size1 >>= 1;
		}
	} else {
		min_fragment = 4;
		if (pcm_file->playback) {
			tmp = ((snd_pcm1_channel_t *) pcm_file->playback->pchn->private_data)->hw.min_fragment;
			if (tmp > min_fragment)
				min_fragment = tmp;
		}
		if (pcm_file->capture) {
			tmp = ((snd_pcm1_channel_t *) pcm_file->capture->pchn->private_data)->hw.min_fragment;
			if (tmp > min_fragment)
				min_fragment = tmp;
		}
		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

	if (pcm_file->playback)
		snd_pcm1_oss_set_fragment1(pcm_file->playback, size, count);
	if (pcm_file->capture)
		snd_pcm1_oss_set_fragment1(pcm_file->capture, size, count);
#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_pcm_file_t * pcm_file)
{
	if (pcm_file->playback &&
	    (((snd_pcm1_subchn_t *)(pcm_file->playback->private_data))->flags & SND_PCM1_FLG_BUFFERS))
		snd_pcm1_oss_compute_blocks(pcm_file->playback);
	if (pcm_file->capture &&
	    (((snd_pcm1_subchn_t *)(pcm_file->capture->private_data))->flags & SND_PCM1_FLG_BUFFERS))
		snd_pcm1_oss_compute_blocks(pcm_file->capture);
	return pcm_file->playback ?
		((snd_pcm1_subchn_t *)(pcm_file->playback->private_data))->block_size :
		((snd_pcm1_subchn_t *)(pcm_file->capture->private_data))->block_size;
}

static int snd_pcm1_oss_set_mode1(snd_pcm_subchn_t * subchn, unsigned int mode)
{
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	unsigned int old_mode;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	if (subchn1->mode != mode) {
		int err;
		
		if (!subchn->capture)
			snd_pcm1_oss_sync_playback(subchn);
		else
			snd_pcm1_oss_sync_capture(subchn);
		subchn1->neutral_byte = (mode & SND_PCM1_MODE_16) ? 0x00 : 0x80;
		old_mode = subchn1->mode;
		subchn1->mode = mode;
		err = pchn1->hw.ioctl(pchn1->private_data, subchn,
				      SND_PCM1_IOCTL_MODE, NULL);
		if (err < 0 && err != -ENXIO) {
			subchn1->neutral_byte = (old_mode & SND_PCM1_MODE_16) ? 0x00 : 0x80;
			subchn1->mode = old_mode;
			pchn1->hw.ioctl(pchn1->private_data, subchn,
				        SND_PCM1_IOCTL_MODE, NULL);
			return err;
		}
		subchn1->flags |= SND_PCM1_FLG_NEUTRAL |
				  SND_PCM1_FLG_BUFFERS;
	}
	return 0;
}

static int snd_pcm1_oss_set_mode(snd_pcm_file_t * pcm_file,
                                  unsigned int mode)
{
	int err;

#ifdef SND_PCM1_DEBUG_BUFFERS
	printk("mode = 0x%x\n", mode);
#endif
	if (pcm_file->playback)
		if ((err = snd_pcm1_oss_set_mode1(pcm_file->playback, mode)) < 0)
			return err;
	if (pcm_file->capture)
		if ((err = snd_pcm1_oss_set_mode1(pcm_file->capture, mode)) < 0)
			return err;
	return 0;
}

static unsigned int snd_pcm1_oss_get_mode(snd_pcm_file_t * pcm_file)
{
	return pcm_file->playback ?
		((snd_pcm1_subchn_t *)(pcm_file->playback->private_data))->mode :
		((snd_pcm1_subchn_t *)(pcm_file->capture->private_data))->mode;
}

static void snd_pcm1_oss_set_format(snd_pcm_file_t * pcm_file,
                                    unsigned int format)
{
#ifdef SND_PCM1_DEBUG_BUFFERS
	printk("format = 0x%x\n", format);
#endif
	if (pcm_file->playback)
		((snd_pcm1_subchn_t *)(pcm_file->playback->private_data))->format = format;
	if (pcm_file->capture)
		((snd_pcm1_subchn_t *)(pcm_file->capture->private_data))->format = format;
}

static unsigned int snd_pcm1_oss_get_format(snd_pcm_file_t * pcm_file)
{
	return pcm_file->playback ?
		((snd_pcm1_subchn_t *)(pcm_file->playback->private_data))->format :
		((snd_pcm1_subchn_t *)(pcm_file->capture->private_data))->format;
}

static int snd_pcm1_oss_set_rate1(snd_pcm_subchn_t *subchn, unsigned int rate)
{
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	unsigned int old_rate;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	if (subchn1->rate != rate) {
		int err;
		if (!subchn->capture)
			snd_pcm1_oss_sync_playback(subchn);
		else
			snd_pcm1_oss_sync_capture(subchn);
		old_rate = subchn1->rate;
		subchn1->rate = rate;
		err = pchn1->hw.ioctl(pchn1->private_data, subchn,
				      SND_PCM1_IOCTL_RATE, NULL);
		if (err < 0 && err != -ENXIO) {
			subchn1->rate = old_rate;
			pchn1->hw.ioctl(pchn1->private_data, subchn,
				        SND_PCM1_IOCTL_RATE, NULL);
			return err;
		}
		subchn1->flags |= SND_PCM1_FLG_BUFFERS;
	}
	return 0;
}

static int snd_pcm1_oss_set_rate(snd_pcm_file_t * pcm_file, unsigned int rate)
{
	int err;

#ifdef SND_PCM1_DEBUG_BUFFERS
	printk("rate = %i\n", rate);
#endif
	if (pcm_file->playback)
		if ((err = snd_pcm1_oss_set_rate1(pcm_file->playback, rate)) < 0)
			return err;
	if (pcm_file->capture)
		if ((err = snd_pcm1_oss_set_rate1(pcm_file->capture, rate)) < 0)
			return err;
	return 0;
}

static unsigned int snd_pcm1_oss_get_rate(snd_pcm_file_t * pcm_file)
{
	return pcm_file->playback ?
		((snd_pcm1_subchn_t *)(pcm_file->playback->private_data))->rate :
		((snd_pcm1_subchn_t *)(pcm_file->capture->private_data))->rate;
}

static int snd_pcm1_oss_format(snd_pcm_file_t * pcm_file, int format)
{
	unsigned int new_mode;
	unsigned int new_format;
	snd_pcm1_subchn_t *playback_subchn1 = NULL, *capture_subchn1 = NULL;
	snd_pcm1_channel_t *playback_pchn1 = NULL, *capture_pchn1 = NULL;
	int err;

	if (pcm_file->playback) {
		playback_subchn1 = (snd_pcm1_subchn_t *) pcm_file->playback->private_data;
		playback_pchn1 = (snd_pcm1_channel_t *) pcm_file->pcm->playback.private_data;
	}
	if (pcm_file->capture) {
		capture_subchn1 = (snd_pcm1_subchn_t *) pcm_file->capture->private_data;
		capture_pchn1 = (snd_pcm1_channel_t *) pcm_file->pcm->capture.private_data;
	}

	if (format != SND_PCM_FMT_QUERY) {
#if 0
		printk("format = 0x%x\n", format);
#endif
		if (!format) {
			new_format = ~snd_pcm1_oss_get_format(pcm_file);
			new_mode = snd_pcm1_oss_get_mode(pcm_file);
		} else {
			new_format = 0;
			if (playback_pchn1) {
				new_format = format & playback_pchn1->hw.formats;
				if (!new_format)	/* not supported format */
					new_format = SND_PCM_FMT_U8;
			}
			if (capture_pchn1 &&
			    ((playback_subchn1 && !(playback_subchn1->flags & SND_PCM1_FLG_MMAP)) ||
			     (capture_subchn1->flags & SND_PCM1_FLG_MMAP) ||
			     playback_subchn1 == NULL)) {
				if (new_format)
					new_format &= capture_pchn1->hw.formats;
				else
					new_format = format & capture_pchn1->hw.formats;
				if (!new_format)	/* not supported format */
					new_format = SND_PCM_FMT_U8;	/* always supported? */
			}
			new_mode = snd_pcm1_oss_get_mode(pcm_file) & ~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(pcm_file)) {
			snd_pcm1_oss_set_format(pcm_file, new_format);
			if ((err = snd_pcm1_oss_set_mode(pcm_file, new_mode)) < 0)
				return err;
		}
	}
	return snd_pcm1_oss_get_format(pcm_file);
}

static int snd_pcm1_oss_rate(snd_pcm_file_t * pcm_file, unsigned int rate)
{
	unsigned int tmp, max_rate;

	if (rate > 0) {
		int err;
		max_rate = 48000;
		if (pcm_file->playback) {
			tmp = ((snd_pcm1_channel_t *)(pcm_file->pcm->playback.private_data))->hw.max_rate;
			if (max_rate > tmp)
				max_rate = tmp;
		}
		if (pcm_file->capture) {
			tmp = ((snd_pcm1_channel_t *)(pcm_file->pcm->capture.private_data))->hw.max_rate;
			if (max_rate > tmp)
				max_rate = tmp;
		}
		if (rate > max_rate)
			rate = max_rate;
		if ((err = snd_pcm1_oss_set_rate(pcm_file, rate)) < 0)
			return err;
	}
	return snd_pcm1_oss_get_rate(pcm_file);
}

static int snd_pcm1_oss_set_channels1(snd_pcm_subchn_t *subchn, int channels)
{
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	int err;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	if (channels > pchn1->hw.max_voices)
		channels = pchn1->hw.max_voices;
	if (!subchn->capture)
		snd_pcm1_oss_sync_playback(subchn);
	else
		snd_pcm1_oss_sync_capture(subchn);
	subchn1->voices = channels;
	err = pchn1->hw.ioctl(pchn1->private_data, subchn,
			      SND_PCM1_IOCTL_VOICES, NULL);
	if (err < 0 && err != -ENXIO)
		return err;
	subchn1->flags |= SND_PCM1_FLG_BUFFERS;
	return channels;
}

static int snd_pcm1_oss_set_channels(snd_pcm_file_t * pcm_file, int channels)
{
	int err;

#if 0
	printk("set channels = %i\n", channels);
#endif
	if (channels >= 0) {
		if (channels < 1 || channels > 32)
			return -EINVAL;
		if (pcm_file->playback) {
			if ((err = snd_pcm1_oss_set_channels1(pcm_file->playback, channels)) < 0) {
				return err;
			} else {
				channels = err;
			}
		}
		if (pcm_file->capture) {
			if ((err = snd_pcm1_oss_set_channels1(pcm_file->capture, channels)) < 0) {
				return err;
			} else {
				channels = err;
			}
		}
	} else {
		return pcm_file->playback ?
			((snd_pcm1_subchn_t *)(pcm_file->playback->private_data))->voices :
			((snd_pcm1_subchn_t *)(pcm_file->capture->private_data))->voices;
	}
	return channels;
}

static void snd_pcm1_oss_reset1(snd_pcm_subchn_t *subchn)
{
	snd_pcm1_subchn_t *subchn1;

	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	subchn1->flags |= SND_PCM1_FLG_ABORT;	/* drain buffers only */
	if (!subchn->capture)
		snd_pcm1_oss_sync_playback(subchn);
	else
		snd_pcm1_oss_sync_capture(subchn);
	subchn1->flags &= ~SND_PCM1_FLG_ABORT;
}

static int snd_pcm1_oss_reset(snd_pcm_file_t * pcm_file)
{
#ifdef SND_PCM1_DEBUG_BUFFERS
	printk("pcm1_reset!!!\n");
#endif
	if (pcm_file->playback)
		snd_pcm1_oss_reset1(pcm_file->playback);
	if (pcm_file->capture)
		snd_pcm1_oss_reset1(pcm_file->capture);
	return 0;
}

static int snd_pcm1_oss_get_trigger(snd_pcm_file_t * pcm_file)
{
	unsigned long flags;
	int result;
	snd_pcm1_subchn_t *subchn1;

	result = 0;
	if (pcm_file->playback) {
		subchn1 = (snd_pcm1_subchn_t *) pcm_file->playback->private_data;
		spin_lock_irqsave(&subchn1->lock, flags);
		if (subchn1->flags & SND_PCM1_FLG_TRIGGER)
			result |= SND_PCM_ENABLE_PLAYBACK;
		spin_unlock_irqrestore(&subchn1->lock, flags);
	}
	if (pcm_file->capture) {
		subchn1 = (snd_pcm1_subchn_t *) pcm_file->capture->private_data;
		spin_lock_irqsave(&subchn1->lock, flags);
		if (subchn1->flags & SND_PCM1_FLG_TRIGGER)
			result |= SND_PCM_ENABLE_CAPTURE;
		spin_unlock_irqrestore(&subchn1->lock, flags);
	}
#if 0
	printk("get trigger = 0x%x\n", result);
#endif
	return result;
}

static int snd_pcm1_oss_set_trigger1(snd_pcm_subchn_t * subchn, int trigger)
{
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	subchn1->flags |= SND_PCM1_FLG_ENABLE;
	if (trigger) {
		if (!(subchn1->flags & SND_PCM1_FLG_TRIGGER)) {
			if (subchn1->flags & SND_PCM1_FLG_BUFFERS)
				snd_pcm1_oss_compute_blocks(subchn);
			if (subchn1->flags & SND_PCM1_FLG_MMAP) {
				if (pchn1->hw.flags & SND_PCM1_HW_8BITONLY) {
					if (subchn1->mode & SND_PCM1_MODE_16)
						return -ENXIO;
				}
				if (pchn1->hw.flags & SND_PCM1_HW_16BITONLY) {
					if (!(subchn1->mode & SND_PCM1_MODE_16))
						return -ENXIO;
				}
				if (!(subchn1->flags & SND_PCM1_FLG_TRIGGER1)) {
					pchn1->hw.prepare(pchn1->private_data,
						 	  subchn,
						 	  subchn1->buffer,
							  subchn1->used_size,
							  0,	/* assuming that first block is always 0 */
							  subchn1->block_size);
				}
				pchn1->hw.trigger(pchn1->private_data, subchn, 1);
				subchn1->flags |= SND_PCM1_FLG_TRIGGERA;
			} else {
				if (!subchn->capture)
					snd_pcm1_oss_trigger_playback(subchn);
				else
					snd_pcm1_oss_trigger_capture(subchn);
			}
		}
	} else {
		subchn1->flags &= ~SND_PCM1_FLG_ENABLE;
		subchn1->flags |= SND_PCM1_FLG_ABORT;
		if (!subchn->capture)
			snd_pcm1_oss_sync_playback(subchn);
		else
			snd_pcm1_oss_sync_capture(subchn);
		subchn1->flags &= ~SND_PCM1_FLG_ABORT;
	}
	return 0;
}

static int snd_pcm1_oss_set_trigger(snd_pcm_file_t *pcm_file, int trigger)
{
	int err;

#if 0
	printk("set trigger = 0x%x\n", trigger);
#endif
	if (pcm_file->playback)
		if ((err = snd_pcm1_oss_set_trigger1(pcm_file->playback, trigger & SND_PCM_ENABLE_PLAYBACK)) < 0)
			return err;
	if (pcm_file->capture)
		if ((err = snd_pcm1_oss_set_trigger1(pcm_file->capture, trigger & SND_PCM_ENABLE_CAPTURE)) < 0)
			return err;
	return snd_pcm1_oss_get_trigger(pcm_file);
}

#if 0
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;
}
#endif

/*
 *  GETOSPACE notes:
 *
 */

static int snd_pcm1_oss_get_space(snd_pcm_file_t * pcm_file,
				  int direction,
                                  struct snd_pcm_buffer_info *arg)
{
	unsigned long flags;
	snd_pcm_subchn_t *subchn = NULL;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	struct snd_pcm_buffer_info info;
	
	if (direction == SND_PCM1_PLAYBACK) {
		if (pcm_file->playback == NULL)
			return 0;
		subchn = pcm_file->playback;
	}
	if (direction == SND_PCM1_CAPTURE) {
		if (pcm_file->capture == NULL)
			return 0;
		subchn = pcm_file->capture;
	}
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	if (subchn1->flags & SND_PCM1_FLG_BUFFERS)
		snd_pcm1_oss_compute_blocks(subchn);
	spin_lock_irqsave(&subchn1->lock, flags);
	info.fragstotal = subchn1->blocks;
	info.fragsize = subchn1->block_size;
	if (!subchn->capture) {
		info.fragments = subchn1->blocks - subchn1->used;
		if (subchn1->frag_size > 0)
			info.fragments--;
	} else {
		info.fragments = subchn1->used;
	}
	if (info.fragments < 0)
		info.fragments = 0;
	info.bytes = info.fragments * info.fragsize;
	if (direction == SND_PCM1_PLAYBACK) {
		if (subchn1->frag_size > 0)
			info.bytes += subchn1->block_size - subchn1->frag_size;
	}
	spin_unlock_irqrestore(&subchn1->lock, flags);
#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
	subchn1->flags |= SND_PCM1_FLG_ENABLE;
	if (copy_to_user(arg, &info, sizeof(info)))
		return -EFAULT;
	return 0;
}

static int snd_pcm1_oss_get_ptr(snd_pcm_file_t * pcm_file,
                                int direction,
                                struct snd_pcm_count_info *arg)
{
	unsigned long flags;
	snd_pcm_subchn_t *subchn = NULL;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	struct snd_pcm_count_info info;
	unsigned int size;

	if (direction == SND_PCM1_PLAYBACK) {
		if (pcm_file->playback == NULL)
			return 0;
		subchn = pcm_file->playback;
	}
	if (direction == SND_PCM1_CAPTURE) {
		if (pcm_file->capture == NULL)
			return 0;
		subchn = pcm_file->capture;
	}
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
	if (subchn1->flags & SND_PCM1_FLG_BUFFERS)
		snd_pcm1_oss_compute_blocks(subchn);
	spin_lock_irqsave(&subchn1->lock, flags);
	info.bytes = subchn1->processed_bytes;
	if (!(subchn1->flags & SND_PCM1_FLG_TRIGGER)) {
		info.ptr = info.bytes == 0 ? subchn1->used_size : 0;
	} else {
		if (pchn1->hw.flags & SND_PCM1_HW_BLOCKPTR) {
			info.ptr = pchn1->hw.pointer(pchn1->private_data,
						     subchn,
						     subchn1->block_size);
			if (info.ptr < 0)
				info.ptr = 0;
			else if (info.ptr >= subchn1->block_size)
				info.ptr = subchn1->block_size - 1;
			info.ptr += subchn1->tail * subchn1->block_size;
		} else {
			info.ptr = pchn1->hw.pointer(pchn1->private_data,
						     subchn,
						     subchn1->used_size);
			if (info.ptr < 0)
				info.ptr = 0;
			else if (info.ptr >= subchn1->used_size)
				info.ptr = subchn1->used_size - 1;
		}
		info.ptr &= ~3;	/* align to right value */
	}
	info.blocks = subchn1->used;
	if (subchn1->flags & SND_PCM1_FLG_MMAP) {
		info.bytes += info.ptr;
		info.blocks = subchn1->interrupts;
		subchn1->interrupts = 0;
	} else {
		if (!info.bytes) {
			size = subchn1->size;
			if (pchn1->hw.flags & SND_PCM1_HW_8BITONLY) {
				if (subchn1->mode & SND_PCM1_MODE_16)
					size <<= 1;
			}
			if (pchn1->hw.flags & SND_PCM1_HW_16BITONLY) {
				if (!(subchn1->mode & SND_PCM1_MODE_16))
					size >>= 1;
			}
			info.bytes = info.ptr = size;
		} else {
			info.bytes += info.ptr % subchn1->block_size;
		}
		info.blocks = subchn1->used;
	}
#if 0
	printk("ptr: flags = 0x%x, bytes = %i, ptr = %i, blocks = %i\n", pchn->flags, info.bytes, info.ptr, info.blocks);
#endif
	spin_unlock_irqrestore(&subchn1->lock, flags);
	if (copy_to_user(arg, &info, sizeof(info)))
		return -EFAULT;
	return 0;
}

static int snd_pcm1_oss_get_mapbuf(snd_pcm_file_t * pcm_file,
				   int direction,
				   struct snd_pcm_buffer_description *arg)
{
	snd_pcm_subchn_t *subchn = NULL;
	snd_pcm1_subchn_t *subchn1;
	struct snd_pcm_buffer_description info;

	if (direction == SND_PCM1_PLAYBACK) {
		if (pcm_file->playback == NULL)
			return 0;
		subchn = pcm_file->playback;
	}
	if (direction == SND_PCM1_CAPTURE) {
		if (pcm_file->capture == NULL)
			return 0;
		subchn = pcm_file->capture;
	}
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	info.buffer = (unsigned char *) subchn1->buffer;
	info.size = subchn1->size;
#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printk("mapbuf%i: size = %i\n", direction, info.size);
#endif
	if (copy_to_user(arg, &info, sizeof(info)))
		return -EFAULT;
	return 0;
}

static int snd_pcm1_oss_caps(snd_pcm_file_t * pcm_file)
{
	unsigned int result;
	snd_pcm_t *pcm;
	snd_pcm1_channel_t *pchn1;

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

static int snd_pcm1_oss_nonblock(snd_pcm_file_t * pcm_file)
{
	if (pcm_file->playback)
		((snd_pcm1_subchn_t *)(pcm_file->playback->private_data))->flags |= SND_PCM1_FLG_NONBLK;
	if (pcm_file->capture)
		((snd_pcm1_subchn_t *)(pcm_file->capture->private_data))->flags |= SND_PCM1_FLG_NONBLK;
	return 0;
}

static int snd_pcm1_oss_odelay(snd_pcm_file_t * pcm_file)
{
	unsigned long flags;
	snd_pcm1_subchn_t *subchn1;
	snd_pcm1_channel_t *pchn1;
	int ptr, result;

	if (pcm_file->playback == NULL)
		return -EIO;
	subchn1 = (snd_pcm1_subchn_t *) pcm_file->playback->private_data;
	pchn1 = (snd_pcm1_channel_t *) pcm_file->playback->pchn->private_data;
	if (subchn1->flags & SND_PCM1_FLG_MMAP)
		return 0;
	spin_lock_irqsave(&subchn1->lock, flags);
	if (pchn1->hw.flags & SND_PCM1_HW_BLOCKPTR) {
		ptr = pchn1->hw.pointer(pchn1->private_data,
					pcm_file->playback,
					subchn1->block_size);
		if (ptr < 0)
			ptr = 0;
		else if (ptr >= subchn1->block_size)
			ptr = subchn1->block_size - 1;
	} else {
		ptr = pchn1->hw.pointer(pchn1->private_data,
			     pcm_file->playback,
			     subchn1->used_size);
		if (ptr < 0)
			ptr = 0;
		else if (ptr >= subchn1->used_size)
			ptr = subchn1->used_size - 1;
		ptr -= subchn1->tail * subchn1->block_size;
		if (ptr < 0)
			ptr = 0;
	}
	ptr &= ~3;		/* align to right value */
	/* compute sum of all waiting bytes to playback */
	result = subchn1->used * subchn1->block_size;
	result += subchn1->frag_size;
	/* decrement current position in fragment */
	result -= ptr;
	spin_unlock_irqrestore(&subchn1->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_pcm_t * pcm,
				  short minor,
				  struct file *file,
				  snd_pcm_file_t **rpcm_file)
{
	int res;
	char task_name[32];
	struct snd_stru_pcm1_oss_setup *psetup = NULL, *csetup = NULL;
	snd_pcm_file_t *pcm_file;
	snd_pcm_subchn_t *playback_subchn = NULL;
	snd_pcm1_subchn_t *playback_subchn1 = NULL;
	snd_pcm1_channel_t *playback_pchn1 = NULL;
	snd_pcm_subchn_t *capture_subchn = NULL;
	snd_pcm1_subchn_t *capture_subchn1 = NULL;
	snd_pcm1_channel_t *capture_pchn1 = NULL;
	int format;
	unsigned int f_mode = file->f_mode;

	*rpcm_file = NULL;
	if (snd_task_name(current, task_name, sizeof(task_name)) < 0)
		return -EFAULT;
	if (f_mode & FMODE_WRITE) {
		playback_pchn1 = (snd_pcm1_channel_t *) pcm->playback.private_data;
		down(&playback_pchn1->setup_mutex);
		psetup = snd_pcm1_oss_look_for_setup(playback_pchn1->setup_list, task_name);
		up(&playback_pchn1->setup_mutex);
	}
	if (psetup && psetup->playback_only)
	    	f_mode &= ~FMODE_READ;
	res = snd_pcm_open(pcm,
			   f_mode & FMODE_WRITE,
			   f_mode & FMODE_READ,
			   &pcm_file);
	if (res < 0) {
		if ((f_mode & FMODE_WRITE) &&
		    !(pcm->info_flags & SND_PCM_INFO_DUPLEX)) {
		    	f_mode = FMODE_WRITE;
			res = snd_pcm_open(pcm,
					   FMODE_WRITE,
					   0,
					   &pcm_file);
		}
		if (res < 0)
			return res;
	}
	if (f_mode & FMODE_WRITE) {
		playback_subchn = pcm_file->playback;
		playback_subchn->oss = 1;
		playback_subchn1 = (snd_pcm1_subchn_t *) snd_kcalloc(sizeof(*playback_subchn1), GFP_KERNEL);
		if (playback_subchn1 == NULL) {
			res = -ENOMEM;
			goto __end;
		}
		init_waitqueue_head(&playback_subchn1->sleep);
		playback_subchn->private_data = playback_subchn1;
		playback_subchn->private_free = snd_pcm1_subchannel_free;
		snd_pcm1_clear_subchannel(playback_subchn);
	}
	if (f_mode & FMODE_READ) {
		capture_subchn = pcm_file->capture;
		capture_subchn->oss = 1;
		capture_subchn1 = (snd_pcm1_subchn_t *) snd_kcalloc(sizeof(*capture_subchn1), GFP_KERNEL);
		if (capture_subchn1 == NULL) {
			res = -ENOMEM;
			goto __end;
		}
		init_waitqueue_head(&capture_subchn1->sleep);
		capture_subchn->private_data = capture_subchn1;
		capture_subchn->private_free = snd_pcm1_subchannel_free;
		capture_pchn1 = (snd_pcm1_channel_t *) pcm->capture.private_data;
		snd_pcm1_clear_subchannel(capture_subchn);
		down(&capture_pchn1->setup_mutex);
		csetup = snd_pcm1_oss_look_for_setup(capture_pchn1->setup_list, task_name);
		up(&capture_pchn1->setup_mutex);
	}
	if (pcm_file->playback) {
		if ((res = playback_pchn1->hw.open(playback_pchn1->private_data,
						   playback_subchn)) < 0)
			goto __end;
		playback_subchn1->flags |= SND_PCM1_FLG_ENABLE;
		playback_subchn1->ack = snd_pcm1_oss_interrupt_playback;
		playback_pchn1->setup = psetup;
	}
	if (pcm_file->capture) {
		if ((res = capture_pchn1->hw.open(capture_pchn1->private_data,
						  capture_subchn)) < 0)
			goto __playback_end;
		capture_subchn1->flags |= SND_PCM1_FLG_ENABLE;
		capture_subchn1->ack = snd_pcm1_oss_interrupt_capture;
		capture_pchn1->setup = csetup;
	}
	snd_pcm1_oss_set_format(pcm_file, 0);
	if ((res = snd_pcm1_oss_set_mode(pcm_file, 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(pcm_file, format)) < 0)
		goto __capture_end;
	if ((res = snd_pcm1_oss_set_rate(pcm_file, SND_PCM_DEFAULT_RATE)) <0)
		goto __capture_end;
	if ((res = snd_pcm1_oss_set_channels(pcm_file, 1)) < 0)
		goto __capture_end;

#if 0
	printk("pcm open - done...\n");
#endif
	*rpcm_file = pcm_file;
	return 0;

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

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

      __end:
      	snd_pcm_release(pcm_file);
      	return res;
}

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

	if (pcm_file == NULL)
		return;
	if (pcm_file->playback) {
		subchn = pcm_file->playback;
		subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
		pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
		pchn1->hw.trigger(pchn1->private_data, subchn, 0); /* for sure */
		pchn1->hw.close(pchn1->private_data, subchn);
	}
	if (pcm_file->capture) {
		subchn = pcm_file->capture;
		subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
		pchn1 = (snd_pcm1_channel_t *) subchn->pchn->private_data;
		pchn1->hw.trigger(pchn1->private_data, subchn, 0); /* for sure */
		pchn1->hw.close(pchn1->private_data, subchn);
	}
	snd_pcm_release(pcm_file);
#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_pcm_file_t *pcm_file;

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

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

	if (file->private_data) {
		pcm_file = (snd_pcm_file_t *) file->private_data;
		if (pcm_file->playback)
			snd_pcm1_oss_sync_playback(pcm_file->playback);
		if (pcm_file->capture)
			snd_pcm1_oss_sync_capture(pcm_file->capture);
		pcm = pcm_file->pcm;
		down(&pcm->open_mutex);
		snd_pcm1_oss_close_card(pcm_file, file);
		up(&pcm->open_mutex);
		pcm->card->use_dec(pcm->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_pcm_file_t *pcm_file;
	int res;

	pcm_file = (snd_pcm_file_t *) file->private_data;
	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')
		return -EIO;
#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(pcm_file);
	case SND_PCM_IOCTL_OSS_SYNC:
		return snd_pcm1_oss_sync(pcm_file);
	case SND_PCM_IOCTL_OSS_RATE:
		res = snd_pcm1_oss_rate(pcm_file, 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(pcm_file));
	case SND_PCM_IOCTL_OSS_STEREO:
		res = snd_pcm1_oss_set_channels(pcm_file, 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(pcm_file));
	case SND_PCM_IOCTL_OSS_FORMAT:
		res = snd_pcm1_oss_format(pcm_file, 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(pcm_file));
	case SND_PCM_IOCTL_OSS_CHANNELS:
		res = snd_pcm1_oss_set_channels(pcm_file, 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(pcm_file, -1));
	case SND_PCM_IOCTL_OSS_FILTER:
	case SND_PCM_IOCTL_OSS_GETFILTER:
		return -EIO;
	case SND_PCM_IOCTL_OSS_POST:	/* to do */
		return 0;
	case SND_PCM_IOCTL_OSS_SUBDIVIDE:
		return snd_ioctl_out((long *) arg, snd_pcm1_oss_set_subdivision(pcm_file, snd_ioctl_in((long *) arg)));
	case SND_PCM_IOCTL_OSS_SETFRAGMENT:
		return snd_ioctl_out((long *) arg, snd_pcm1_oss_set_fragment(pcm_file, snd_ioctl_in((long *) arg)));
	case SND_PCM_IOCTL_OSS_GETFORMATS:
		return snd_ioctl_out((long *) arg,
			pcm_file->playback ?
				((snd_pcm1_channel_t *) pcm_file->playback->pchn->private_data)->hw.formats :
				((snd_pcm1_channel_t *) pcm_file->capture->pchn->private_data)->hw.formats);
	case SND_PCM_IOCTL_OSS_GETPBKSPACE:
	case SND_PCM_IOCTL_OSS_GETRECSPACE:
		return snd_pcm1_oss_get_space(pcm_file,
			cmd == SND_PCM_IOCTL_OSS_GETRECSPACE ? SND_PCM1_CAPTURE : SND_PCM1_PLAYBACK,
			(struct snd_pcm_buffer_info *) arg);
	case SND_PCM_IOCTL_OSS_NONBLOCK:
		return snd_pcm1_oss_nonblock(pcm_file);
	case SND_PCM_IOCTL_OSS_GETCAPS:
		return snd_ioctl_out((long *) arg, snd_pcm1_oss_caps(pcm_file));
	case SND_PCM_IOCTL_OSS_GETTRIGGER:
		return snd_ioctl_out((long *) arg, snd_pcm1_oss_get_trigger(pcm_file));
	case SND_PCM_IOCTL_OSS_SETTRIGGER:
		res = snd_pcm1_oss_set_trigger(pcm_file, 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(pcm_file,
			cmd == SND_PCM_IOCTL_OSS_GETRECPTR ? SND_PCM1_CAPTURE : 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(pcm_file,
			cmd == SND_PCM_IOCTL_OSS_MAPRECBUFFER ? SND_PCM1_CAPTURE : 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(pcm_file) & SND_PCM_CAP_DUPLEX)
			return 0;
		return -EIO;
	case SND_PCM_IOCTL_OSS_GETODELAY:
		if (pcm_file->playback == NULL)
			return -EIO;
		return snd_ioctl_out((long *) arg, snd_pcm1_oss_odelay(pcm_file));
	case SND_PCM_IOCTL_OSS_PROFILE:
		return 0;	/* silently ignore */
#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_pcm_file_t *pcm_file;

	pcm_file = (snd_pcm_file_t *) file->private_data;
	if (pcm_file == NULL || pcm_file->capture == NULL)
		return -EIO;
	return snd_pcm1_oss_buffer_to_user(pcm_file->capture, buf, count);
}

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

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

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

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

	if (pcm_file->capture) {
		capture_subchn = pcm_file->capture;
		capture_subchn1 = (snd_pcm1_subchn_t *) capture_subchn->private_data;
		capture_pchn1 = (snd_pcm1_channel_t *) capture_subchn->pchn->private_data;
		if (capture_subchn1->flags & SND_PCM1_FLG_BUFFERS)
			snd_pcm1_oss_compute_blocks(capture_subchn);
	}
	if (pcm_file->playback) {
		playback_subchn = pcm_file->playback;
		playback_subchn1 = (snd_pcm1_subchn_t *) playback_subchn->private_data;
		playback_pchn1 = (snd_pcm1_channel_t *) playback_subchn->pchn->private_data;
		if (playback_subchn1->flags & SND_PCM1_FLG_BUFFERS)
			snd_pcm1_oss_compute_blocks(playback_subchn);
	}
	if (pcm_file->capture) {
		snd_pcm1_oss_trigger_capture(pcm_file->capture);
		poll_wait(file, &capture_subchn1->sleep, wait);
	}
	if (pcm_file->playback)
		poll_wait(file, &playback_subchn1->sleep, wait);
	mask = 0;
	if (pcm_file->capture) {
		if (capture_subchn1->flags & SND_PCM1_FLG_MMAP) {
			if (capture_subchn1->interrupts)
				mask |= POLLIN | POLLRDNORM;
		} else {
			if (capture_subchn1->used)
				mask |= POLLIN | POLLRDNORM;
		}
	}
	if (pcm_file->playback) {
		if (playback_subchn1->flags & SND_PCM1_FLG_MMAP) {
			if (playback_subchn1->interrupts)
				mask |= POLLOUT | POLLWRNORM;
		} else {
			if (playback_subchn1->used < playback_subchn1->blocks)
				mask |= POLLOUT | POLLWRNORM;
		}
	}
	return mask;
}

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_file_t *pcm_file;
	unsigned long size;
	snd_dma_area_t *pdma;
	snd_pcm_subchn_t *subchn;
	snd_pcm1_subchn_t *subchn1;

	pcm_file = (snd_pcm_file_t *) file->private_data;
	if (!(pcm_file->pcm->info_flags & SND_PCM_INFO_MMAP))
		return -EIO;
	if ((vma->vm_flags & (VM_READ | VM_WRITE)) == (VM_READ | VM_WRITE))
		return -EINVAL;
	if (vma->vm_flags & VM_READ) {
		subchn = pcm_file->capture;
	} else if (vma->vm_flags & VM_WRITE) {
		subchn = pcm_file->playback;
	} else {
		return -EINVAL;
	}
	if (subchn == NULL)
		return -EINVAL;
	subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	pdma = subchn1->dmaptr;
	if (!pdma)
		return -ENOENT;
	if (vma->vm_offset != 0)
		return -EINVAL;
	size = vma->vm_end - vma->vm_start;
	if (size != subchn1->size && size != subchn1->used_size) {
		snd_printk("snd-pcm-oss: wrong mmap() size 0x%x, should be 0x%x\n", (unsigned int) size, subchn1->size);
	}
	subchn1->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(subchn1->buffer),
			     size,
			     vma->vm_page_prot))
		return -EAGAIN;
	if (vma->vm_ops)
		return -EINVAL;	/* Hmm... shouldn't happen */
	vma->vm_file = file;
	vma->vm_ops = &snd_pcm1_oss_vm_ops;
	MOD_INC_USE_COUNT;
	pdma->mmaped = 1;
	pdma->vma = vma;
	subchn1->flags |= SND_PCM1_FLG_MMAP;
	snd_pcm1_fill_with_neutral(subchn);
#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,
                                    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_pcm_t *pcm = (snd_pcm_t *) private_data;
	snd_pcm1_channel_t * pchn1;

	pchn1 = (snd_pcm1_channel_t *) pcm->playback.private_data;
	down(&pchn1->setup_mutex);
	snd_pcm1_oss_proc_read1(buffer, pchn1->setup_list, "Playback");
	up(&pchn1->setup_mutex);
	pchn1 = (snd_pcm1_channel_t *) pcm->capture.private_data;
	down(&pchn1->setup_mutex);
	snd_pcm1_oss_proc_read1(buffer, pchn1->setup_list, "Capture");
	up(&pchn1->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_kfree(setup->task_name);
		snd_kfree(setup);
	}
	pchn->setup_list = NULL;
	pchn->setup = NULL;
}

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

	while (!snd_info_get_line(buffer, line, sizeof(line))) {
		pchn1 = NULL;
		if (!strncmp(line, "Playback ", 9)) {
			pchn1 = (snd_pcm1_channel_t *) pcm->playback.private_data;
			idx = 9;
		} else if (!strncmp(line, "Capture ", 8)) {
			pchn1 = (snd_pcm1_channel_t *) pcm->capture.private_data;
			idx = 8;
		} else {
			buffer->error = -EINVAL;
			continue;
		}
		down(&pchn1->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(pchn1);
			up(&pchn1->setup_mutex);
			continue;
		}
		for (setup = pchn1->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_kmalloc(sizeof(struct snd_stru_pcm1_oss_setup), GFP_KERNEL);
			if (setup) {
				if (!pchn1->setup_list) {
					pchn1->setup_list = setup;
				} else {
					for (setup1 = pchn1->setup_list; setup1->next; setup1 = setup1->next);
					setup1->next = setup;
				}
				template.task_name = snd_kmalloc_strdup(task_name, GFP_KERNEL);
			} else {
				buffer->error = -ENOMEM;
			}
		}
		if (setup)
			memcpy(setup, &template, sizeof(template));
		up(&pchn1->setup_mutex);
	}
}

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

	sprintf(name, "pcmD%io", pcm->device);
	if ((entry = snd_info_create_entry(pcm->card, name)) != NULL) {
		entry->private_data = pcm;
		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);
	}
	pcm->proc_oss_entry = entry;
}

static void snd_pcm1_oss_proc_done(snd_pcm_t * pcm)
{
	if (pcm->proc_oss_entry) {
		snd_info_store_text(pcm->proc_oss_entry);
		snd_info_unregister(pcm->proc_oss_entry);
		pcm->proc_oss_entry = NULL;
		snd_pcm1_oss_proc_free_setup_list((snd_pcm1_channel_t *)pcm->playback.private_data);
		snd_pcm1_oss_proc_free_setup_list((snd_pcm1_channel_t *)pcm->capture.private_data);
	}
}

/*
 *  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 */
	snd_pcm1_oss_poll,	/* poll */
	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[128];

	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 {
			if (pcm->device == 0) {
				sprintf(name, "%s%s", pcm->name, pcm->info_flags & SND_PCM_INFO_DUPLEX ? " (DUPLEX)" : "");
				snd_oss_info_register(SND_OSS_INFO_DEV_AUDIO,
						      pcm->card->number,
						      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);
		if (pcm->device == 0)
			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;

	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);
}
