/*
 *  Digital Audio (PCM) abstract layer
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "../include/driver.h"
#include "../include/control.h"
#include "../include/info.h"
#include "../include/pcm.h"
#include "../include/timer.h"

/*
 *  DMA management
 */

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

	if ((err = snd_dma_malloc(subchn->pcm->card, dma, ident, &area)) < 0)
		return err;
	if (area->size < 4096) {
		snd_printd("Invalid audio DMA size - %li\n", area->size);
		snd_dma_free(subchn->pcm->card, area);
		return -ENOMEM;
	}
	return snd_pcm_dma_setup(subchn, area);
}

int snd_pcm_dma_setup(snd_pcm_subchn_t * subchn, snd_dma_area_t * area)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;

	if (area == NULL)
		return -EINVAL;
	runtime->dma_area = area;
	runtime->flags |= SND_PCM_FLG_DMA_OK;
	return 0;
}

int snd_pcm_dma_free(snd_pcm_subchn_t * subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;

	if (!(runtime->flags & SND_PCM_FLG_DMA_OK))
		return -EINVAL;
	snd_dma_free(subchn->pcm->card, runtime->dma_area);
	runtime->dma_area = NULL;
	runtime->flags &= ~SND_PCM_FLG_DMA_OK;
	return 0;
}

/*
 *  Sync
 */
 
void snd_pcm_set_sync(snd_pcm_subchn_t * subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	
	runtime->sync.id32[0] = subchn->pcm->card->type;
	runtime->sync.id32[1] = subchn->pcm->card->number;
}

void snd_pcm_set_mixer(snd_pcm_subchn_t * subchn,
		       int mixer_device,
		       snd_kmixer_element_t *element)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	
	runtime->mixer_device = mixer_device;
	memset(&runtime->mixer_eid, 0, sizeof(runtime->mixer_eid));
	strncpy(runtime->mixer_eid.name, element->name, sizeof(runtime->mixer_eid.name));
	runtime->mixer_eid.index = element->index;
	runtime->mixer_eid.type = element->type;
}

/*
 *  Counters
 */

unsigned int snd_pcm_lib_transfer_size(snd_pcm_subchn_t *subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	
	if (runtime->mode == SND_PCM_MODE_STREAM) {
		return runtime->buf.stream.queue_size;
	} else if (runtime->mode == SND_PCM_MODE_BLOCK) {
		return runtime->frags * runtime->buf.block.frag_size;
	}
	return 0;
}

unsigned int snd_pcm_lib_transfer_fragment(snd_pcm_subchn_t *subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	
	if (runtime->mode == SND_PCM_MODE_STREAM) {
		return runtime->buf.stream.queue_size;
	} else if (runtime->mode == SND_PCM_MODE_BLOCK) {
		return runtime->buf.block.frag_size;
	}
	return 0;
}

int snd_pcm_lib_interleave_len(snd_pcm_subchn_t *subchn)
{
	return snd_pcm_format_size(subchn->runtime->format.format,
				   subchn->runtime->format.voices);
}

/*
 *  Standard ioctl routine
 */

static inline unsigned int snd_pcm_lib_get_stream_ptr(snd_pcm_subchn_t *subchn, snd_pcm_runtime_t *runtime)
{
	unsigned int ptr;

	ptr = subchn->runtime->hw->pointer(subchn->pchn->private_data, subchn);
	ptr += runtime->buf_position;
	while (ptr < runtime->last_position)
		ptr += runtime->buf.stream.queue_size;
	return runtime->last_position = ptr;
}

static inline unsigned int snd_pcm_lib_get_block_ptr(snd_pcm_subchn_t *subchn, snd_pcm_runtime_t *runtime)
{
	unsigned int ptr;

	ptr = subchn->runtime->hw->pointer(subchn->pchn->private_data, subchn);
	ptr += runtime->buf_position;
	while (ptr < runtime->last_position)
		ptr += runtime->buf.block.frag_size;
	return runtime->last_position = ptr;
}

int snd_pcm_lib_fragment(long size, snd_pcm_hardware_t *hw)
{
	if (size < hw->min_fragment_size)
		size = hw->min_fragment_size;
	if (size > hw->max_fragment_size)
		size = hw->max_fragment_size;
	size += hw->fragment_align;
	size /= (hw->fragment_align + 1);
	size *= (hw->fragment_align + 1);
	if (size == 0)
		size = hw->fragment_align + 1;
	return size;
}

int snd_pcm_lib_set_buffer_size(snd_pcm_subchn_t *subchn, long size)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int tmp;

	if (runtime->mode == SND_PCM_MODE_BLOCK) {
		if (size < runtime->buf.block.frag_size) {
			runtime->buf.block.frag_size = snd_pcm_lib_fragment(size, runtime->hw);
			runtime->frags = 1;
		}
		else
			runtime->frags = size / runtime->buf.block.frag_size;
		if (runtime->format.interleave) {
			tmp = 128;
		} else {
			tmp = 128 / runtime->format.voices;
		}
		if (tmp <= 0)
			return -EINVAL;
#ifdef CONFIG_SND_OSSEMUL
		if (subchn->oss.oss && runtime->buf.block.frags_max + 1 < tmp)
			tmp = runtime->buf.block.frags_max + 1;
#endif
		if (runtime->frags > tmp)
			runtime->frags = tmp;
		if (runtime->frags == 1) {
			runtime->buf.block.frag_size = snd_pcm_lib_fragment(runtime->buf.block.frag_size / 2, runtime->hw);
			runtime->frags++;
		}
		if (runtime->buf.block.frags_min < 0)
			runtime->buf.block.frags_min = 0;
		if (runtime->buf.block.frags_min >= runtime->frags)
			runtime->buf.block.frags_min = runtime->frags - 1;
		if (runtime->buf.block.frags_max < 0)
			runtime->buf.block.frags_max = runtime->frags + runtime->buf.block.frags_max;
		if (runtime->buf.block.frags_max < runtime->buf.block.frags_min)
			runtime->buf.block.frags_max = runtime->buf.block.frags_min;
		if (runtime->buf.block.frags_max < 1)
			runtime->buf.block.frags_max = 1;
		if (runtime->buf.block.frags_max > runtime->frags)
			runtime->buf.block.frags_max = runtime->frags;
	} else if (runtime->mode == SND_PCM_MODE_STREAM) {
		if (size < runtime->buf.stream.queue_size)
			runtime->buf.stream.queue_size = size;
		runtime->buf.stream.queue_size = snd_pcm_lib_fragment(runtime->buf.stream.queue_size, runtime->hw);
		if (runtime->buf.stream.max_fill > runtime->buf.stream.queue_size)
			runtime->buf.stream.max_fill = runtime->buf.stream.queue_size;
	}
	return 0;
}

#if 0
#define DEBUG_PARAMS
#endif

static int snd_pcm_lib_ioctl_params(void *private_data,
				    snd_pcm_subchn_t *subchn,
				    unsigned long *arg)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_pcm_hardware_t *hw = runtime->hw;
	unsigned int ui32;
	
	/* check transfer mode */
#ifdef DEBUG_PARAMS
	printk("params - transfer mode\n");
#endif
	if (runtime->mode == SND_PCM_MODE_STREAM) {
		if (!(hw->chninfo & SND_PCM_CHNINFO_STREAM)) {
			runtime->mode = SND_PCM_MODE_UNKNOWN;
			return -EINVAL;
		}
	} else if (runtime->mode == SND_PCM_MODE_BLOCK) {
		if (!(hw->chninfo & SND_PCM_CHNINFO_BLOCK)) {
			runtime->mode = SND_PCM_MODE_UNKNOWN;
			return -EINVAL;
		}
	} else {
		runtime->mode = SND_PCM_MODE_UNKNOWN;
		return -EINVAL;
	}
	/* check interleave flag */
#ifdef DEBUG_PARAMS
	printk("params - interleave\n");
#endif
	if (runtime->format.voices > 1) {
		if (runtime->format.interleave) {
			if (!(hw->chninfo & SND_PCM_CHNINFO_INTERLEAVE))
				if (runtime->mode == SND_PCM_MODE_BLOCK)
					return -EINVAL;
		} else {
			if (runtime->mode == SND_PCM_MODE_STREAM)
				return -EINVAL;
			if (!(hw->chninfo & SND_PCM_CHNINFO_NONINTERLEAVE))
				return -EINVAL;
		}
	}
	/* check format */
#ifdef DEBUG_PARAMS
	printk("params - format (%i)\n", runtime->format.format);
#endif
	if (!((1 << runtime->format.format) & hw->formats))
		return -EINVAL;
	/* check rate */
#ifdef DEBUG_PARAMS
	printk("params - rate\n");
#endif
	if (hw->rates & (SND_PCM_RATE_CONTINUOUS|SND_PCM_RATE_KNOT)) {
		if (runtime->format.rate < hw->min_rate ||
		    runtime->format.rate > hw->max_rate)
			return -EINVAL;
	} else {
		switch (runtime->format.rate) {
		case 8000:	ui32 = SND_PCM_RATE_8000; break;
		case 11025:	ui32 = SND_PCM_RATE_11025; break;
		case 16000:	ui32 = SND_PCM_RATE_16000; break;
		case 22050:	ui32 = SND_PCM_RATE_22050; break;
		case 32000:	ui32 = SND_PCM_RATE_32000; break;
		case 44100:	ui32 = SND_PCM_RATE_44100; break;
		case 48000:	ui32 = SND_PCM_RATE_48000; break;
		case 88200:	ui32 = SND_PCM_RATE_88200; break;
		case 96000:	ui32 = SND_PCM_RATE_96000; break;
		case 176400:	ui32 = SND_PCM_RATE_176400; break;
		case 192000:	ui32 = SND_PCM_RATE_192000; break;
		default:	ui32 = 0; break;
		}
		if (!(hw->rates & ui32))
			return -EINVAL;
	}
	/* check voices */
#ifdef DEBUG_PARAMS
	printk("params - voices\n");
#endif
	if (runtime->format.voices < hw->min_voices ||
	    runtime->format.voices > hw->max_voices)
		return -EINVAL;
	/* check stream buffer parameters */
#ifdef DEBUG_PARAMS
	printk("params - buffer parameters\n");
#endif
	if (runtime->mode == SND_PCM_MODE_STREAM) {
		if (runtime->buf.stream.queue_size < 4)
			return -EINVAL;
	} else if (runtime->mode == SND_PCM_MODE_BLOCK) {
		runtime->buf.block.frag_size = snd_pcm_lib_fragment(runtime->buf.block.frag_size, hw);
	}
	if (runtime->dma_area)
		snd_pcm_lib_set_buffer_size(subchn, runtime->dma_area->size);
	return 0;
}

static int snd_pcm_lib_ioctl_setup(void *private_data,
				   snd_pcm_subchn_t *subchn,
				   unsigned long *arg)
{
	snd_pcm_channel_setup_t *setup = (snd_pcm_channel_setup_t *)arg;
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int res;

	res = snd_pcm_format_width(runtime->format.format);
	setup->msbits_per_sample = res < 0 ? 0 : res;
	return 0;
}

static int snd_pcm_lib_ioctl_status(void *private_data,
				    snd_pcm_subchn_t *subchn,
				    unsigned long *arg)
{
	snd_pcm_channel_status_t *status = (snd_pcm_channel_status_t *)arg;
	snd_pcm_runtime_t *runtime = subchn->runtime;
	unsigned long flags;
	unsigned int ptr, tmpu;
	int stop = 0;

	spin_lock_irqsave(&runtime->lock, flags);
	if (runtime->flags & SND_PCM_FLG_TIME) {
		memcpy(&status->stime, &runtime->stime, sizeof(struct timeval));
	} else {
		status->stime.tv_sec = status->stime.tv_usec = 0;
	}
	status->ust_stime = 0;	/* TODO */
	if (runtime->mode == SND_PCM_MODE_BLOCK) {
		if (subchn->channel == SND_PCM_CHANNEL_PLAYBACK) {
			status->frag = *runtime->frag_tail;
		} else {
			status->frag = *runtime->frag_head;
		}
		ptr = snd_pcm_lib_get_block_ptr(subchn, runtime);
		status->scount = runtime->buf_position;
		status->scount += ptr % runtime->buf.block.frag_size;
		if (!(runtime->flags & SND_PCM_FLG_OSS_MMAP)) {
			if (runtime->flags & SND_PCM_FLG_MMAP) {
				status->count = status->free = 0;
			} else {
				status->count = runtime->frag_used * runtime->buf.block.frag_size;
				status->free = (runtime->frags - runtime->frag_used) * runtime->buf.block.frag_size;
				if (subchn->channel == SND_PCM_CHANNEL_PLAYBACK) {
					status->count -= ptr % runtime->buf.block.frag_size;
					if (status->count < 0)
						status->count = 0;
				} else {
					status->count += ptr % runtime->buf.block.frag_size;
				}
			}
		} else {
			status->count = runtime->interrupts;
			runtime->interrupts = 0;
			status->free = 0;
		}
	} else if (runtime->mode == SND_PCM_MODE_STREAM) {
		ptr = snd_pcm_lib_get_stream_ptr(subchn, runtime);
		status->scount = ptr;
		status->frag = 0;
		if (subchn->channel == SND_PCM_CHANNEL_PLAYBACK) {
			status->count = runtime->position - status->scount;
		} else {
			status->count = status->scount - runtime->position;
		}
		if (status->count < 0) {
			status->count = 0;
			stop++;
		} else if (status->count > runtime->buf.stream.queue_size) {
			status->count = runtime->buf.stream.queue_size;
		}
		if ((tmpu = runtime->hw->transfer_block_size) > 1) {
			if (ptr > tmpu) {
				ptr -= tmpu;
			} else {
				ptr = 0;
			}
		}
		if (subchn->channel == SND_PCM_CHANNEL_PLAYBACK) {
			status->free = (ptr + runtime->buf.stream.queue_size) - runtime->position;
		} else {
			if (ptr < runtime->position)
				ptr = runtime->position;
			status->free = (runtime->position + runtime->buf.stream.queue_size) - ptr;
		}
		if (status->free < 0) {
			status->free = 0;
			stop++;
		} else if (status->free > runtime->buf.stream.queue_size) {
			status->free = runtime->buf.stream.queue_size;
			stop++;
		}
	}
	status->underrun = runtime->underrun; runtime->underrun = 0;
	status->overrun = runtime->overrun; runtime->overrun = 0;
	status->overrange = runtime->overrange; runtime->overrange = 0;
	spin_unlock_irqrestore(&runtime->lock, flags);	
	if (stop && runtime->stop_mode != SND_PCM_STOP_ROLLOVER)
		snd_pcm_transfer_stop(subchn,
				subchn->channel == SND_PCM_CHANNEL_PLAYBACK ?
					SND_PCM_STATUS_UNDERRUN :
					SND_PCM_STATUS_OVERRUN);
	return 0;
}

int snd_pcm_lib_mmap_ctrl_ptr(snd_pcm_subchn_t *subchn, char *ptr)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_pcm_mmap_control_t *ctrl = runtime->mmap_control;
	snd_pcm_mmap_fragment_t *frag;
	int idx, voice, voice_size = 0;

	snd_debug_check(ptr == NULL, -EINVAL);
	ctrl->status.frags = runtime->frags;
	if (runtime->format.interleave) {
		ctrl->status.frag_size = runtime->buf.block.frag_size;
		ctrl->status.voices = -1;
	} else {
		voice_size = runtime->buf.block.frag_size / runtime->format.voices;
		ctrl->status.frag_size = voice_size;
		ctrl->status.voices = runtime->format.voices;
	}
	for (idx = 0; idx < runtime->frags; idx++) {
		if (runtime->format.interleave) {
			frag = &ctrl->fragments[idx];
			frag->number = idx;
			frag->addr = idx * runtime->buf.block.frag_size;
			frag->voice = -1;
			frag->data = 0;
			frag->io = 0;
			frag->res[0] = frag->res[1] = 0;
		} else {
			for (voice = 0; voice < runtime->format.voices; voice++) {
				frag = &ctrl->fragments[(voice * runtime->frags) + idx];
				frag->number = idx;
				frag->addr = (idx * runtime->buf.block.frag_size) + (voice * voice_size);
				frag->voice = voice;
				frag->data = 0;
				frag->io = 0;
				frag->res[0] = frag->res[1] = 0;
			}
		}
	}
	return 0;
}

static int snd_pcm_lib_ioctl_mmap_ctrl(void *private_data,
				       snd_pcm_subchn_t *subchn,
				       unsigned long *arg)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;

	if (runtime->dma_area) {
		return snd_pcm_lib_mmap_ctrl_ptr(subchn, runtime->dma_area->buf);
	} else {
		return -EIO;
	}
}

static int snd_pcm_lib_ioctl_mmap_size(void *private_data,
				       snd_pcm_subchn_t *subchn,
				       unsigned long *arg)
{
	long *size = (long *)arg;
	snd_pcm_runtime_t *runtime = subchn->runtime;

	if (runtime->dma_area) {
		*size = runtime->dma_area->size;
	} else {
		*size = 0;
		return -ENXIO;
	}
	return 0;
}

static int snd_pcm_lib_ioctl_mmap_ptr(void *private_data,
				      snd_pcm_subchn_t *subchn,
				      unsigned long *arg)
{
	char **ptr = (char **)arg;
	snd_pcm_runtime_t *runtime = subchn->runtime;

	if (runtime->dma_area) {
		*ptr = runtime->dma_area->buf;
	} else {
		*ptr = NULL;
		return -ENXIO;
	}
	return 0;
}

static int snd_pcm_lib_ioctl_pause(void *private_data,
				   snd_pcm_subchn_t *subchn,
				   unsigned long *arg)
{
	unsigned long flags;
	int cmd, res;
	snd_pcm_runtime_t *runtime = subchn->runtime;

	if (!(runtime->hw->chninfo & SND_PCM_CHNINFO_PAUSE))
		return 0;
	cmd = *arg ? SND_PCM_TRIGGER_PAUSE_PUSH : SND_PCM_TRIGGER_PAUSE_RELEASE;
	spin_lock_irqsave(&runtime->lock, flags);
	if (*runtime->status != SND_PCM_STATUS_RUNNING) {
		res = -EBADFD;
	} else {
		res = runtime->hw->trigger(private_data, subchn, cmd);
	}
	if (res >= 0) {
		if (*arg) {
			*runtime->status = SND_PCM_STATUS_PAUSED;
		} else {
			*runtime->status = SND_PCM_STATUS_RUNNING;
		}
	}
	spin_unlock_irqrestore(&runtime->lock, flags);
	return res;
}

int snd_pcm_lib_ioctl(void *private_data, snd_pcm_subchn_t *subchn,
		      unsigned int cmd, unsigned long *arg)
{
	switch (cmd) {
	case SND_PCM_IOCTL1_INFO:
		return 0;
	case SND_PCM_IOCTL1_PARAMS:
		return snd_pcm_lib_ioctl_params(private_data, subchn, arg);
	case SND_PCM_IOCTL1_SETUP:
		return snd_pcm_lib_ioctl_setup(private_data, subchn, arg);
	case SND_PCM_IOCTL1_STATUS:
		return snd_pcm_lib_ioctl_status(private_data, subchn, arg);
	case SND_PCM_IOCTL1_MMAP_CTRL:
		return snd_pcm_lib_ioctl_mmap_ctrl(private_data, subchn, arg);
	case SND_PCM_IOCTL1_MMAP_SIZE:
		return snd_pcm_lib_ioctl_mmap_size(private_data, subchn, arg);
	case SND_PCM_IOCTL1_MMAP_PTR:
		return snd_pcm_lib_ioctl_mmap_ptr(private_data, subchn, arg);
	case SND_PCM_IOCTL1_PAUSE:
		return snd_pcm_lib_ioctl_pause(private_data, subchn, arg);
	}
	return -ENXIO;
}

/*
 *  Conditions
 */

int snd_pcm_playback_ok(snd_pcm_subchn_t *subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int result;
	unsigned int ptr, expblock;
	unsigned long flags;
	
	if (runtime->flags & SND_PCM_FLG_OSS_MMAP)
		return runtime->interrupts > 0 ? 1 : 0;
	spin_lock_irqsave(&runtime->lock, flags);
	if (runtime->mode == SND_PCM_MODE_BLOCK) {
		if (runtime->flags & SND_PCM_FLG_MMAP) {
			ptr = runtime->mmap_control->status.block;
			expblock = runtime->mmap_control->status.expblock;
			if (ptr + runtime->frags < runtime->frags ||
			    expblock + runtime->frags < runtime->frags) {
			    	ptr -= 1024 * 1024;
			    	expblock -= 1024 * 1024;
			}
			result = ptr >= expblock;
		} else {
			result = runtime->frag_used <= runtime->buf.block.frags_max &&
				 (runtime->frags - runtime->frag_used) >= runtime->buf.block.frags_min;
		}
	} else {
		ptr = snd_pcm_lib_get_stream_ptr(subchn, runtime);
		result = runtime->position - ptr > 0;
	}
	spin_unlock_irqrestore(&runtime->lock, flags);
	return result;
}

int snd_pcm_capture_ok(snd_pcm_subchn_t *subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int result;
	unsigned int ptr, expblock;
	unsigned long flags;
	
	if (runtime->flags & SND_PCM_FLG_OSS_MMAP)
		return runtime->interrupts > 0 ? 1 : 0;
	spin_lock_irqsave(&runtime->lock, flags);
	if (runtime->mode == SND_PCM_MODE_BLOCK) {
		if (runtime->flags & SND_PCM_FLG_MMAP) {
			ptr = runtime->mmap_control->status.block;
			expblock = runtime->mmap_control->status.expblock;
			if (ptr + runtime->frags < runtime->frags ||
			    expblock + runtime->frags < runtime->frags) {
			    	ptr -= 1024 * 1024;
			    	expblock -= 1024 * 1024;
			}
			result = ptr >= expblock;
		} else {
			result = runtime->frag_used >= runtime->buf.block.frags_min;
		}
	} else {
		ptr = snd_pcm_lib_get_stream_ptr(subchn, runtime);
		result = ptr - runtime->position < runtime->buf.stream.queue_size;
	}
	spin_unlock_irqrestore(&runtime->lock, flags);
	return result;
}

int snd_pcm_playback_data(snd_pcm_subchn_t *subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int result;
	unsigned int ptr;
	unsigned long flags;
	
	if (runtime->flags & SND_PCM_FLG_OSS_MMAP)
		return runtime->interrupts > 0 ? 1 : 0;
	spin_lock_irqsave(&runtime->lock, flags);
	if (runtime->mode == SND_PCM_MODE_BLOCK) {
		if (runtime->flags & SND_PCM_FLG_MMAP) {
			if (*runtime->status == SND_PCM_STATUS_UNDERRUN) {
				result = 0;
			} else {
				result = runtime->mmap_control->fragments[*runtime->frag_tail].data ? 1 : 0;
			}
		} else {
			result = runtime->frag_used > 0;
		}
	} else {
		ptr = snd_pcm_lib_get_stream_ptr(subchn, runtime);
		result = runtime->position > ptr;
	}
	spin_unlock_irqrestore(&runtime->lock, flags);
	return result;
}

int snd_pcm_playback_empty(snd_pcm_subchn_t *subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int result;
	unsigned long flags;
	
	if (runtime->flags & SND_PCM_FLG_OSS_MMAP)
		return 1;
	spin_lock_irqsave(&runtime->lock, flags);
	if (runtime->mode == SND_PCM_MODE_BLOCK) {
		if (runtime->flags & SND_PCM_FLG_MMAP) {
			if (*runtime->status == SND_PCM_STATUS_UNDERRUN) {
				result = 1;
			} else {
				result = runtime->mmap_control->fragments[*runtime->frag_tail].data ? 0 : 1;
			}
		} else {
			result = runtime->frag_used ? 0 : 1;
		}
	} else {
		result = 0;
		if (*runtime->status == SND_PCM_STATUS_RUNNING) {
			unsigned int ptr = snd_pcm_lib_get_stream_ptr(subchn, runtime);
			if (ptr >= runtime->position) {
				runtime->position = runtime->buf_position + (ptr % runtime->buf.stream.queue_size);
				spin_unlock_irqrestore(&runtime->lock, flags);
				snd_pcm_transfer_stop(subchn, SND_PCM_STATUS_UNDERRUN);
				return 1;
			}
		} else if (runtime->position == 0 ||
			   runtime->buf_position >= runtime->position ||
			   *runtime->status == SND_PCM_STATUS_UNDERRUN) {
			result = 1;
		}
	}
	spin_unlock_irqrestore(&runtime->lock, flags);
	return result;
}

int snd_pcm_capture_empty(snd_pcm_subchn_t *subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int result;
	unsigned int ptr;
	unsigned long flags;
	
	if (runtime->flags & SND_PCM_FLG_OSS_MMAP)
		return runtime->interrupts == 0;
	spin_lock_irqsave(&runtime->lock, flags);
	if (runtime->mode == SND_PCM_MODE_BLOCK) {
		result = runtime->frag_used == 0;
	} else {
		ptr = snd_pcm_lib_get_stream_ptr(subchn, runtime);
		result = runtime->position >= ptr;
	}
	spin_unlock_irqrestore(&runtime->lock, flags);
	return result;
}

void snd_pcm_clear_values(snd_pcm_subchn_t *subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	
	runtime->interrupts = 0;
	runtime->position = 0;
	runtime->buf_position = 0;
	runtime->last_position = 0;
	runtime->fill_position = 0;
	if (runtime->flags & SND_PCM_FLG_MMAP) {
		runtime->mmap_control->status.block = 0;
		runtime->mmap_control->status.expblock = 0;
	}
}

/*
 *  Standard transfer routines
 */

void snd_pcm_transfer_stop(snd_pcm_subchn_t *subchn, int status)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;

	runtime->hw->trigger(subchn->pchn->private_data, subchn, SND_PCM_TRIGGER_STOP);
	*runtime->frag_tail = *runtime->frag_head = runtime->frag_used = 0;
	*runtime->status = status;
}

static void snd_pcm_lib_boundary_playback(snd_pcm_runtime_t *runtime)
{
	unsigned int used;

	if (runtime->mode == SND_PCM_MODE_BLOCK) {
		used = (runtime->buf.block.frag_size * runtime->frags) * 4;
		runtime->buf_position %= used;
		runtime->last_position %= used;
	} else if (runtime->mode == SND_PCM_MODE_STREAM) {
#ifdef CONFIG_SND_DEBUG
		if (runtime->position < runtime->buf_position)
			snd_printk("snd_pcm_boundary_playback: position = %u, buf_position = %u\n", runtime->position, runtime->buf_position);
#endif
		/* there may be some waiting interrupts */
		used = runtime->buf.stream.queue_size * 8;
		runtime->buf_position %= used;
		runtime->position %= used;
		runtime->last_position %= used;
		runtime->fill_position %= used;
		if (runtime->position < runtime->buf_position)
			runtime->position += used;
		if (runtime->fill_position < runtime->buf_position)
			runtime->fill_position += used;
	}
}

static void snd_pcm_lib_boundary_capture(snd_pcm_runtime_t *runtime)
{
	unsigned int used;

	if (runtime->mode == SND_PCM_MODE_BLOCK) {
		used = (runtime->buf.block.frag_size * runtime->frags) * 4;
		runtime->buf_position %= used;
		runtime->last_position %= used;
	} else if (runtime->mode == SND_PCM_MODE_STREAM) {
#ifdef CONFIG_SND_DEBUG
		if (runtime->position > runtime->buf_position)
			snd_printk("snd_pcm_boundary_playback: position = %u, buf_position = %u\n", runtime->position, runtime->buf_position);
#endif
		used = runtime->buf.stream.queue_size * 8;
		runtime->buf_position %= used;
		runtime->position %= used;
		runtime->last_position %= used;
		if (runtime->position > runtime->buf_position)
			runtime->buf_position += used;
	}
}

void snd_pcm_transfer_done(snd_pcm_subchn_t *subchn)
{
	snd_pcm_runtime_t *runtime;

	if (subchn == NULL) {
		snd_printd("snd_pcm_transfer_done: subchn == NULL\n");
		return;
	}
	runtime = subchn->runtime;
	if (runtime == NULL) {
		snd_printd("snd_pcm_transfer_done: runtime == NULL\n");
		return;
	}
	if (runtime->transfer_ack_begin)
		runtime->transfer_ack_begin(subchn);
	if (subchn->channel == SND_PCM_CHANNEL_PLAYBACK) {
		int tail;
	
		spin_lock_irq(&runtime->lock);
		if (*runtime->status != SND_PCM_STATUS_RUNNING)
			goto __p_unlock;
		switch (runtime->mode) {
		case SND_PCM_MODE_BLOCK:
			if (runtime->frags <= 0) {
				snd_printd("PCM transfer done: frags = 0\n");
				goto __p_unlock;
			}
			tail = *runtime->frag_tail;
			if (runtime->flags & SND_PCM_FLG_MMAP) {
				if (tail < 0)
					tail = 0;
				if (tail > 127)
					tail = 127;
				runtime->mmap_control->fragments[tail].io = 0;
				runtime->mmap_control->fragments[tail].data = 0;
				if (!runtime->format.interleave) {
					int voice, frag;

					voice = (tail / runtime->frags) + 1;
					frag = tail % runtime->frags;
					if (voice >= runtime->format.voices) {
						voice = 0;
						frag++;
						frag %= runtime->frags;
						runtime->mmap_control->status.block++;
					}
					tail = (voice * runtime->frags) + frag;
				} else {
					tail++;
					tail %= runtime->frags;
					runtime->mmap_control->status.block++;
				}
				*runtime->frag_tail = tail;
				runtime->mmap_control->fragments[tail].io = 1;
				runtime->buf_position += runtime->buf.block.frag_size;
				if (runtime->buf_position > SND_PCM_BOUNDARY)
					snd_pcm_lib_boundary_playback(runtime);
				if (!runtime->mmap_control->fragments[tail].data) {
					runtime->underrun++;
					if (runtime->stop_mode != SND_PCM_STOP_ROLLOVER) {
						spin_unlock_irq(&runtime->lock);
						snd_pcm_transfer_stop(subchn, SND_PCM_STATUS_UNDERRUN);
						goto __p_wakeup;
					}
				}
				if (runtime->mmap_data == NULL)
					goto __p_unlock;
				spin_unlock_irq(&runtime->lock);
				snd_pcm_proc_write(subchn, runtime->buf_position,
				                   runtime->mmap_data + runtime->mmap_control->fragments[tail].addr,
				                   runtime->mmap_control->status.frag_size, 1);
				goto __p_wakeup;
			}
			tail++;
			tail %= runtime->frags;
			*runtime->frag_tail = tail;
			runtime->buf_position += runtime->buf.block.frag_size;
			if (runtime->buf_position > SND_PCM_BOUNDARY)
				snd_pcm_lib_boundary_playback(runtime);
			if (runtime->flags & SND_PCM_FLG_OSS_MMAP) {
				runtime->interrupts++;
				goto __p_unlock;
			}
			if (runtime->frag_used > 0) {
				runtime->frag_used--;
				if (runtime->frag_used == 0) {
					*runtime->frag_head = *runtime->frag_tail;
					runtime->underrun++;
					if (runtime->stop_mode != SND_PCM_STOP_ROLLOVER) {
						spin_unlock_irq(&runtime->lock);
						snd_pcm_transfer_stop(subchn, SND_PCM_STATUS_UNDERRUN);
						goto __p_wakeup;
					}
				}
			}
			break;
		case SND_PCM_MODE_STREAM:
			runtime->buf_position += runtime->buf.stream.queue_size;
			if (runtime->buf_position > SND_PCM_BOUNDARY)
				snd_pcm_lib_boundary_playback(runtime);
			if (runtime->buf_position >= runtime->position) {
				runtime->underrun++;
				if (runtime->stop_mode != SND_PCM_STOP_ROLLOVER) {
					spin_unlock_irq(&runtime->lock);
					snd_pcm_transfer_stop(subchn, SND_PCM_STATUS_UNDERRUN);
					return;
				}
			}
			spin_unlock_irq(&runtime->lock);
			return;
		}
	      __p_unlock:
		spin_unlock_irq(&runtime->lock);
	      __p_wakeup:
	      	if (runtime->timer_running)
			snd_timer_interrupt(subchn->timer, 1);
		if (snd_pcm_playback_ok(subchn))
			wake_up(&runtime->sleep);
	} else if (subchn->channel == SND_PCM_CHANNEL_CAPTURE) {
		int head;

		spin_lock_irq(&runtime->lock);
		if (*runtime->status != SND_PCM_STATUS_RUNNING)
			goto __c_unlock;
		switch (runtime->mode) {
		case SND_PCM_MODE_BLOCK:
			if (runtime->frags <= 0) {
				snd_printd("PCM transfer done: frags = 0\n");
				goto __c_unlock;
			}
			head = *runtime->frag_head;
			if (runtime->flags & SND_PCM_FLG_MMAP) {
				if (head < 0)
					head = 0;
				if (head > 127)
					head = 127;
				runtime->mmap_control->fragments[head].io = 0;
				runtime->mmap_control->fragments[head].data = 1;
				if (!runtime->format.interleave) {
					int voice, frag;
				
					voice = (head / runtime->frags) + 1;
					frag = head % runtime->frags;
					if (voice >= runtime->format.voices) {
						voice = 0;
						frag++;
						frag %= runtime->frags;
						runtime->mmap_control->status.block++;
					}
					head = (voice * runtime->frags) + frag;
				} else {
					head++;
					head %= runtime->frags;
					runtime->mmap_control->status.block++;
				}
				*runtime->frag_head = head;
				runtime->mmap_control->fragments[head].io = 1;
				runtime->buf_position += runtime->buf.block.frag_size;
				if (runtime->buf_position > SND_PCM_BOUNDARY)
					snd_pcm_lib_boundary_capture(runtime);
				if (runtime->mmap_control->fragments[head].data) {
					runtime->overrun++;
					if (runtime->stop_mode != SND_PCM_STOP_ROLLOVER) {
						spin_unlock_irq(&runtime->lock);
						snd_pcm_transfer_stop(subchn, SND_PCM_STATUS_OVERRUN);
						goto __c_wakeup;
					}
				}
				if (runtime->mmap_data == NULL)
					goto __c_unlock;
				spin_unlock_irq(&runtime->lock);
				snd_pcm_proc_write(subchn, runtime->buf_position,
				                   runtime->mmap_data + runtime->mmap_control->fragments[head].addr,
				                   runtime->mmap_control->status.frag_size, 1);
				goto __c_wakeup;
			}
			head++;
			head %= runtime->frags;
			*runtime->frag_head = head;
			runtime->buf_position += runtime->buf.block.frag_size;
			if (runtime->buf_position > SND_PCM_BOUNDARY)
				snd_pcm_lib_boundary_capture(runtime);
			if (runtime->flags & SND_PCM_FLG_OSS_MMAP) {
				runtime->interrupts++;
				goto __p_unlock;
			}
			if (runtime->frag_used < runtime->frags) {
				runtime->frag_used++;
			} else {
				*runtime->frag_tail = *runtime->frag_head;
				runtime->overrun++;
				if (runtime->stop_mode != SND_PCM_STOP_ROLLOVER) {
					spin_unlock_irq(&runtime->lock);
					snd_pcm_transfer_stop(subchn, SND_PCM_STATUS_OVERRUN);
					goto __c_wakeup;
				}
			}
			break;
		case SND_PCM_MODE_STREAM:
			runtime->buf_position += runtime->buf.stream.queue_size;
			if (runtime->buf_position > SND_PCM_BOUNDARY)
				snd_pcm_lib_boundary_capture(runtime);
			if (runtime->buf_position >= runtime->position + runtime->buf.stream.queue_size) {
				runtime->overrun++;
				if (runtime->stop_mode != SND_PCM_STOP_ROLLOVER) {
					spin_unlock_irq(&runtime->lock);
					snd_pcm_transfer_stop(subchn, SND_PCM_STATUS_OVERRUN);
					return;
				}
			}
			spin_unlock_irq(&runtime->lock);
			return;
		}
	      __c_unlock:
		spin_unlock_irq(&runtime->lock);
	      __c_wakeup:
	      	if (runtime->timer_running)
			snd_timer_interrupt(subchn->timer, 1);
		if (snd_pcm_capture_ok(subchn))
			wake_up(&runtime->sleep);
	}
	if (runtime->transfer_ack_end)
		runtime->transfer_ack_end(subchn);
}

static int snd_pcm_lib_fill(snd_pcm_subchn_t *subchn, unsigned int ptr,
			    void (*silence)(snd_pcm_subchn_t *subchn,
			    		    unsigned int dstoff,
			    		    unsigned int size,
			    		    unsigned char silencebyte))
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	unsigned int xfree, len;
	unsigned char silencebyte;
	unsigned long flags;
	
	spin_lock_irqsave(&runtime->lock, flags);
	silencebyte = snd_pcm_format_silence(runtime->format.format);
	if (runtime->fill_position < runtime->position)
		runtime->fill_position = runtime->position;
	xfree = (ptr + runtime->buf.stream.queue_size) - runtime->position;
	if (xfree == 0)
		goto __end;
	if (runtime->buf.stream.fill == SND_PCM_FILL_SILENCE)
		if (xfree > runtime->buf.stream.max_fill)
			xfree = runtime->buf.stream.max_fill;
	xfree -= runtime->fill_position - runtime->position;
	if (xfree >= runtime->buf.stream.queue_size) {
		spin_unlock_irqrestore(&runtime->lock, flags);
		snd_pcm_transfer_stop(subchn, SND_PCM_STATUS_UNDERRUN);
		return -EPIPE;
	}
	while (xfree > 0 && *runtime->status != SND_PCM_STATUS_UNDERRUN) {
		len = xfree;
		if ((runtime->fill_position % runtime->buf.stream.queue_size) + len > runtime->buf.stream.queue_size)
			len = runtime->buf.stream.queue_size - (runtime->fill_position % runtime->buf.stream.queue_size);
		spin_unlock_irqrestore(&runtime->lock, flags);
		silence(subchn, runtime->fill_position % runtime->buf.stream.queue_size, len, silencebyte);
		spin_lock_irqsave(&runtime->lock, flags);
		xfree -= len;
		runtime->fill_position += len;
	}
      __end:
	spin_unlock_irqrestore(&runtime->lock, flags);
	return 0;
}

static void snd_pcm_lib_write_silence(snd_pcm_subchn_t *subchn,
				      unsigned int dstoff,
				      unsigned int size,
				      unsigned char silencebyte)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;

	if (runtime->hw_memset) {
		runtime->hw_memset(subchn, -1, dstoff, silencebyte, size);
	} else {
		memset(runtime->dma_area->buf + dstoff, silencebyte, size);
	}
}

static int snd_pcm_lib_write_transfer(snd_pcm_subchn_t *subchn,
				      unsigned int dstoff,
				      const void *src, unsigned int srcoff,
				      unsigned int size)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int err;
	if (runtime->hw_memcpy) {
		if ((err = runtime->hw_memcpy(subchn, -1, dstoff, (char *) src + srcoff, size)) < 0)
			return err;
	} else {
		if (copy_from_user(runtime->dma_area->buf + dstoff, (const char *) src + srcoff, size))
			return -EFAULT;
	}
	snd_pcm_proc_write(subchn, runtime->position, (const char *) src + srcoff, size, 0);
	return 0;
}
 
static long snd_pcm_lib_write_block(snd_pcm_subchn_t *subchn, const void *src, unsigned long count, int nonblock,
				    int (*transfer)(snd_pcm_subchn_t *subchn, unsigned int dstoff,
						    const void *src, unsigned int srcoff, unsigned int size))
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	unsigned long flags;
	long result = 0, timeout;
	long offset = 0;
	int err;
	int block;
	int frag_size = runtime->buf.block.frag_size;
	
	if (count % frag_size != 0)
		return -EINVAL;
	while (count > 0) {
		while (!snd_pcm_playback_ok(subchn)) {
			if (*runtime->status == SND_PCM_STATUS_PAUSED)
				return result > 0 ? result : -EAGAIN;
			if (runtime->start_mode == SND_PCM_START_FULL)
				snd_pcm_kernel_playback_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_GO, 0);
			if (*runtime->status != SND_PCM_STATUS_RUNNING)
				return result > 0 ? result : -EPIPE;
			if (nonblock)
				return result > 0 ? result : -EAGAIN;
			timeout = interruptible_sleep_on_timeout(&runtime->sleep, 10 * HZ);
			if (signal_pending(current))
				return result > 0 ? result : -EINTR;
			if (!timeout && !snd_pcm_playback_ok(subchn)) {
				snd_printd("playback write error (DMA or IRQ trouble?)\n");
				return result > 0 ? result : -EIO;
			}
		}
		spin_lock_irqsave(&runtime->lock, flags);
		if (*runtime->status != SND_PCM_STATUS_PREPARED &&
		    *runtime->status != SND_PCM_STATUS_RUNNING) {
			spin_unlock_irqrestore(&runtime->lock, flags);
			// snd_printd("playback write error (status = %i)\n", *runtime->status);
			return result > 0 ? result : -EIO;
		}
		block = (*runtime->frag_head)++;
		*runtime->frag_head %= runtime->frags;
		runtime->frag_used++;
		spin_unlock_irqrestore(&runtime->lock, flags);
		if ((err = transfer(subchn, frag_size * block, src, offset, frag_size)) < 0)
			return result > 0 ? result : err;
		offset += frag_size;
		count -= frag_size;
		result += frag_size;
		runtime->position += frag_size;
		if (runtime->position > SND_PCM_BOUNDARY)
			snd_pcm_lib_boundary_playback(runtime);
	}
	if (result > 0 && runtime->start_mode == SND_PCM_START_DATA)
		snd_pcm_kernel_playback_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_GO, 0);
	return result;
}

static long snd_pcm_lib_write_stream(snd_pcm_subchn_t *subchn, const void *src, unsigned long count, int nonblock,
				     int (*transfer)(snd_pcm_subchn_t *subchn, unsigned int dstoff,
						     const void *src, unsigned int srcoff, unsigned int size),
				     void (*silence)(snd_pcm_subchn_t *subchn, unsigned int dstoff,
				     		     unsigned int size, unsigned char silencebyte))
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	unsigned long flags;
	long result = 0;
	long offset = 0;
	int err;
	unsigned int ptr, tmpu;
	int tmp, size, size1;
	
	if (!runtime->format.interleave)
		return -EINVAL;
	if ((tmp = snd_pcm_lib_interleave_len(subchn)) > 0) {
		count /= tmp;
		if (count == 0)
			return -EINVAL;
		count *= tmp;
	}
	spin_lock_irqsave(&runtime->lock, flags);
	// printk("write: count = %li, buf_position = %i, position = %i, queue_size = %i\n", count, runtime->buf_position, runtime->position, runtime->buf.stream.queue_size);
	if (*runtime->status == SND_PCM_STATUS_RUNNING) {
		ptr = snd_pcm_lib_get_stream_ptr(subchn, runtime);
		if (tmp > 1) {
			ptr += tmp-1; ptr /= tmp; ptr *= tmp;
		}
		// printk("ptr = %i\n", ptr);
		if (ptr > runtime->position) {
			runtime->position = runtime->buf_position + (ptr % runtime->buf.stream.queue_size);
			if (runtime->stop_mode != SND_PCM_STOP_ROLLOVER) {
				spin_unlock_irqrestore(&runtime->lock, flags);
				snd_pcm_transfer_stop(subchn, SND_PCM_STATUS_UNDERRUN);
				return result > 0 ? result : -EPIPE;
			}
		}
	} else {
		if (*runtime->status == SND_PCM_STATUS_UNDERRUN) {
			spin_unlock_irqrestore(&runtime->lock, flags);
			return -EPIPE;
		}
		ptr = 0;
	}
	if ((tmpu = runtime->hw->transfer_block_size) > 1) {
		if (ptr > tmpu) {
			ptr -= tmpu;
		} else {
			ptr = 0;
		}
	}
	while (count > 0) {
		size = runtime->buf.stream.queue_size - (runtime->position - ptr);
		if (count < size)
			size = count;
		size1 = runtime->buf.stream.queue_size - (runtime->position % runtime->buf.stream.queue_size);
		if (size1 < size)
			size = size1;
		if (size <= 0)
			break;
		// printk("size = %i, size1 = %i, count = %i, ptr = %i\n", size, size1, count, ptr);
		// printk("write: count = %li, buf_position = %i, position = %i, queue_size = %i\n", count, runtime->buf_position, runtime->position, runtime->buf.stream.queue_size);
		spin_unlock_irqrestore(&runtime->lock, flags);
		if ((err = transfer(subchn, runtime->position % runtime->buf.stream.queue_size, src, offset, size)) < 0)
			return result > 0 ? result : err;
		spin_lock_irqsave(&runtime->lock, flags);
		if (*runtime->status == SND_PCM_STATUS_UNDERRUN) {
			result = result > 0 ? result : -EPIPE;
			break;
		}
		offset += size;
		count -= size;
		result += size;
		runtime->position += size;
		if (runtime->position > SND_PCM_BOUNDARY)
			snd_pcm_lib_boundary_playback(runtime);		
	}
	spin_unlock_irqrestore(&runtime->lock, flags);
	if (result >= 0 && runtime->buf.stream.fill != SND_PCM_FILL_NONE) {
		if ((err = snd_pcm_lib_fill(subchn, ptr, silence)) < 0)
			return result > 0 ? result : err;
	}
	if (result > 0 && runtime->start_mode == SND_PCM_START_DATA)
		snd_pcm_kernel_playback_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_GO, 0);
	else if (runtime->position == runtime->buf.stream.queue_size && runtime->start_mode == SND_PCM_START_FULL)
		snd_pcm_kernel_playback_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_GO, 0);
	else if (result == 0)
		result = -EAGAIN;
	return result;
}

long snd_pcm_lib_write(snd_pcm_subchn_t *subchn, const char *buf, long count)
{
	snd_pcm_runtime_t *runtime;
	int nonblock;

	snd_debug_check(subchn == NULL, -ENXIO);
	runtime = subchn->runtime;
	snd_debug_check(runtime == NULL, -ENXIO);
	snd_debug_check(runtime->dma_area == NULL, -EINVAL);
	if (*runtime->status < SND_PCM_STATUS_PREPARED)
		return -EBADFD;

	snd_debug_check(subchn->ffile == NULL, -ENXIO);
	nonblock = !!(subchn->ffile->f_flags & O_NONBLOCK);
#ifdef CONFIG_SND_OSSEMUL
	if (subchn->oss.oss) {
		snd_pcm_oss_setup_t *setup = subchn->oss.setup;
		if (setup != NULL) {
			if (setup->nonblock)
				nonblock = 1;
			else if (setup->block)
				nonblock = 0;
		}
	}
#endif

	if (runtime->mode == SND_PCM_MODE_BLOCK) {
		return snd_pcm_lib_write_block(subchn, buf, count, nonblock,
					       snd_pcm_lib_write_transfer);
	} else if (runtime->mode == SND_PCM_MODE_STREAM) {
		return snd_pcm_lib_write_stream(subchn, buf, count, nonblock,
						snd_pcm_lib_write_transfer,
						snd_pcm_lib_write_silence);
	}
	return -EIO;
}

static void snd_pcm_lib_writev_silence(snd_pcm_subchn_t *subchn,
				       unsigned int dstoff,
				       unsigned int size,
				       unsigned char silencebyte)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int voices = runtime->format.voices;
	int v;

	size /= voices;
	dstoff /= voices;
	if (runtime->hw_memset) {
		for (v = 0; v < voices; v++)
			runtime->hw_memset(subchn, v, dstoff, silencebyte, size);
	} else {
		/* default silence behaviour */
		int buf_size = runtime->buf.stream.queue_size / voices;
		for (v = 0; v < voices; v++)
			memset(runtime->dma_area->buf + (v * buf_size) + dstoff, silencebyte, size);
	}
}

static int snd_pcm_lib_writev_transfer(snd_pcm_subchn_t *subchn,
				       unsigned int dstoff,
				       const void *src, unsigned int srcoff,
				       unsigned int size)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int err;
	char * const *bufs = src;
	int voices = runtime->format.voices;
	int v;
	size /= voices;
	srcoff /= voices;
	dstoff /= voices;
	if (runtime->hw_memcpy) {
		snd_debug_check(runtime->hw_memset == NULL, -EINVAL);
		for (v = 0; v < voices; ++v) {
			if (bufs[v] == NULL) {
				if ((err = runtime->hw_memset(subchn, v, dstoff, snd_pcm_format_silence(runtime->format.format), size)) < 0)
					return err;
				snd_pcm_proc_write_silence(subchn, runtime->position, size);
			} else {
				if ((err = runtime->hw_memcpy(subchn, v, dstoff, bufs[v] + srcoff, size)) < 0)
					return err;
				snd_pcm_proc_write(subchn, runtime->position, bufs[v] + srcoff, size, 0);
			}
		}
	} else {
		/* default transfer behaviour */
		int buf_size = runtime->buf.stream.queue_size / voices;
		for (v = 0; v < voices; ++v) {
			char *dest = runtime->dma_area->buf + (v * buf_size) + dstoff;
			if (bufs[v] == NULL) {
				memset(dest, snd_pcm_format_silence(runtime->format.format), size);
				snd_pcm_proc_write_silence(subchn, runtime->position, size);
			} else {
				if (copy_from_user(dest, bufs[v] + srcoff, size))
					return -EFAULT;
				snd_pcm_proc_write(subchn, runtime->position, bufs[v] + srcoff, size, 0);
			}
		}
	}
	return 0;
}
 
long snd_pcm_lib_writev(snd_pcm_subchn_t *subchn, const struct iovec *vector,
			unsigned long vcount)
{
	snd_pcm_runtime_t *runtime;
	int nonblock;
	int voices;
	int b, bcount;
	int result = 0;

	snd_debug_check(subchn == NULL, -ENXIO);
	runtime = subchn->runtime;
	snd_debug_check(runtime == NULL, -ENXIO);
	snd_debug_check(runtime->dma_area == NULL, -EINVAL);
	if (*runtime->status < SND_PCM_STATUS_PREPARED)
		return -EBADFD;

	snd_debug_check(subchn->ffile == NULL, -ENXIO);
	nonblock = !!(subchn->ffile->f_flags & O_NONBLOCK);
#ifdef CONFIG_SND_OSSEMUL
	if (subchn->oss.oss) {
		snd_pcm_oss_setup_t *setup = subchn->oss.setup;
		if (setup != NULL) {
			if (setup->nonblock)
				nonblock = 1;
			else if (setup->block)
				nonblock = 0;
		}
	}
#endif

	if (runtime->format.interleave) {
		for (b = 0; b < vcount; b++) {
			char *buf = vector[b].iov_base;
			size_t len = vector[b].iov_len;
			int ret;
			if (runtime->mode == SND_PCM_MODE_BLOCK)
				ret = snd_pcm_lib_write_block(subchn, buf, len, nonblock,
							      snd_pcm_lib_write_transfer);
			else if (runtime->mode == SND_PCM_MODE_STREAM)
				ret = snd_pcm_lib_write_stream(subchn, buf, len, nonblock,
							       snd_pcm_lib_write_transfer,
							       snd_pcm_lib_write_silence);
			else
				return -EIO;
			if (ret < 0)
				return result > 0 ? result : ret;
			if (ret != len)
				break;
			result += ret;
		}
	} else {
		voices = runtime->format.voices;
		if (vcount % voices)
			return -EINVAL;
		bcount = vcount / voices;
		for (b = 0; b < bcount; ++b) {
			int ret;
			int v;
			int count = 0;
			char *bufs[voices];
			for (v = 0; v < voices; ++v) {
				size_t len = vector[v].iov_len;
				bufs[v] = vector[v].iov_base;
				if (v == 0)
					count = len;
				else if (count != len)
					return -EINVAL;
				if (bufs[v] != NULL && verify_area(VERIFY_READ, bufs[v], len))
					return -EFAULT;
			}
			if (runtime->mode == SND_PCM_MODE_BLOCK)
				ret = snd_pcm_lib_write_block(subchn, bufs, count * voices, nonblock,
							      snd_pcm_lib_writev_transfer);
			else if (runtime->mode == SND_PCM_MODE_STREAM)
				ret = snd_pcm_lib_write_stream(subchn, bufs, count * voices, nonblock,
							       snd_pcm_lib_writev_transfer,
							       snd_pcm_lib_writev_silence);
			else
				return -EIO;
			if (ret < 0)
				return result > 0 ? result : ret;
			result += ret;
			if (ret != count * voices)
				break;
			vector += voices;	
		}
	}
	return result;
}

int snd_pcm_lib_read_transfer(snd_pcm_subchn_t *subchn,
			      void *dst, unsigned int dstoff, 
			      unsigned int srcoff, unsigned int size)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int err;
	if (runtime->hw_memcpy) {
		if ((err = runtime->hw_memcpy(subchn, -1, srcoff, (char *)dst + dstoff, size)) < 0)
			return err;
	} else {
		if (copy_to_user((char *) dst + dstoff, runtime->dma_area->buf + srcoff, size))
			return -EFAULT;
	}
	snd_pcm_proc_write(subchn, runtime->position, (char *) dst + dstoff, size, 0);
	return 0;
}

static long snd_pcm_lib_read_block(snd_pcm_subchn_t *subchn, void *dst, unsigned long count, int nonblock,
				   int (*transfer)(snd_pcm_subchn_t *subchn,
						   void *dst, unsigned int dstoff, 
						   unsigned int srcoff, unsigned int size))
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	unsigned long flags;
	long result = 0, timeout;
	long offset = 0;
	int err;
	int block;
	int frag_size = runtime->buf.block.frag_size;
	
	if (count % frag_size != 0)
		return -EINVAL;
	while (count > 0) {
		while (!snd_pcm_capture_ok(subchn)) {
			if (runtime->start_mode == SND_PCM_START_DATA)
				snd_pcm_kernel_capture_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_GO, 0);
			if (*runtime->status != SND_PCM_STATUS_RUNNING)
				return result > 0 ? result : -EPIPE;
			if (nonblock)
				return result > 0 ? result : -EAGAIN;
			timeout = interruptible_sleep_on_timeout(&runtime->sleep, 10 * HZ);
			if (signal_pending(current))
				return result > 0 ? result : -EINTR;
			if (!timeout && !snd_pcm_capture_ok(subchn)) {
				snd_printd("capture read error (DMA or IRQ trouble?)\n");
				return result > 0 ? result : -EIO;
			}
		}
		spin_lock_irqsave(&runtime->lock, flags);
		if (runtime->frag_used == 0 &&
		    *runtime->status != SND_PCM_STATUS_PREPARED &&
		    *runtime->status != SND_PCM_STATUS_RUNNING) {
			spin_unlock_irqrestore(&runtime->lock, flags);
			// snd_printd("capture read error (status = %i)\n", *runtime->status);
			return result > 0 ? result : -EPIPE;
		}
		block = (*runtime->frag_tail)++;
		*runtime->frag_tail %= runtime->frags;
		runtime->frag_used--;
		spin_unlock_irqrestore(&runtime->lock, flags);
		if ((err = transfer(subchn, dst, offset, frag_size * block, frag_size)) < 0)
			return result > 0 ? result : err;
		offset += frag_size;
		count -= frag_size;
		result += frag_size;
		runtime->position += frag_size;
		if (runtime->position > SND_PCM_BOUNDARY)
			snd_pcm_lib_boundary_capture(runtime);
	}
	return result;
}


static long snd_pcm_lib_read_stream(snd_pcm_subchn_t *subchn, void *dst, unsigned long count, int nonblock,
				    int (*transfer)(snd_pcm_subchn_t *subchn,
						    void *dst, unsigned int dstoff, 
						    unsigned int srcoff, unsigned int size))
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	unsigned long flags;
	long result = 0;
	long offset = 0;
	int err;
	unsigned int ptr, tmpu;
	int tmp, size, size1;
	
	if (!runtime->format.interleave)
		return -EINVAL;
	if ((tmp = snd_pcm_lib_interleave_len(subchn)) > 0) {
		count /= tmp;
		if (count == 0)
			return -EINVAL;
		count *= tmp;
	}
	spin_lock_irqsave(&runtime->lock, flags);
	// printk("read: count = %li, buf_position = %i, position = %i, queue_size = %i\n", count, runtime->buf_position, runtime->position, runtime->buf.stream.queue_size);
	if (*runtime->status == SND_PCM_STATUS_RUNNING) {
		ptr = snd_pcm_lib_get_stream_ptr(subchn, runtime);
		if (tmp > 1) {
			ptr += tmp-1; ptr /= tmp; ptr *= tmp;
		}
		if (ptr > runtime->position + runtime->buf.stream.queue_size) {
			runtime->position = runtime->buf_position + (ptr % runtime->buf.stream.queue_size);
			if (runtime->stop_mode != SND_PCM_STOP_ROLLOVER) {
				spin_unlock_irqrestore(&runtime->lock, flags);
				snd_pcm_transfer_stop(subchn, SND_PCM_STATUS_OVERRUN);
				return -EPIPE;
			}
		}
	} else {
		if (*runtime->status != SND_PCM_STATUS_PREPARED) {
			spin_unlock_irqrestore(&runtime->lock, flags);
			return -EPIPE;
		}
		if (runtime->start_mode == SND_PCM_START_DATA) {
			spin_unlock_irqrestore(&runtime->lock, flags);
			snd_pcm_kernel_capture_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_GO, 0);
			spin_lock_irqsave(&runtime->lock, flags);
		}
		ptr = 0;
	}
	if ((tmpu = runtime->hw->transfer_block_size) > 1) {
		if (ptr > tmpu) {
			ptr -= tmpu;
			if (ptr < runtime->position)
				ptr = runtime->position;
		} else {
			ptr = 0;
		}
	}
	while (count > 0) {
		size = ptr - runtime->position;
		if (count < size)
			size = count;
		size1 = runtime->buf.stream.queue_size - (runtime->position % runtime->buf.stream.queue_size);
		if (size1 < size)
			size = size1;
		if (size <= 0)
			break;
		// printk("size = %i, size1 = %i, count = %i, ptr = %i\n", size, size1, count, ptr);
		// printk("write: count = %li, buf_position = %i, position = %i, queue_size = %i\n", count, runtime->buf_position, runtime->position, runtime->buf.stream.queue_size);
		spin_unlock_irqrestore(&runtime->lock, flags);
		if ((err = transfer(subchn, dst, offset, runtime->position % runtime->buf.stream.queue_size, size)) < 0)
			return result > 0 ? result : err;
		spin_lock_irqsave(&runtime->lock, flags);
		if (*runtime->status != SND_PCM_STATUS_RUNNING) {
			result = result > 0 ? result : -EPIPE;
			break;
		}
		offset += size;
		count -= size;
		result += size;
		runtime->position += size;
		if (runtime->position > SND_PCM_BOUNDARY)
			snd_pcm_lib_boundary_capture(runtime);
	}
	spin_unlock_irqrestore(&runtime->lock, flags);
	return result == 0 ? -EAGAIN : result;
}

long snd_pcm_lib_read(snd_pcm_subchn_t *subchn, char *buf, long count)
{
	snd_pcm_runtime_t *runtime;
	int nonblock;
	
	snd_debug_check(subchn == NULL, -ENXIO);
	runtime = subchn->runtime;
	snd_debug_check(runtime == NULL, -ENXIO);
	snd_debug_check(runtime->dma_area == NULL, -EINVAL);
	if (*runtime->status < SND_PCM_STATUS_PREPARED)
		return -EBADFD;

	snd_debug_check(subchn->ffile == NULL, -ENXIO);
	nonblock = !!(subchn->ffile->f_flags & O_NONBLOCK);
#ifdef CONFIG_SND_OSSEMUL
	if (subchn->oss.oss) {
		snd_pcm_oss_setup_t *setup = subchn->oss.setup;
		if (setup != NULL) {
			if (setup->nonblock)
				nonblock = 1;
			else if (setup->block)
				nonblock = 0;
		}
	}
#endif
	if (runtime->mode == SND_PCM_MODE_BLOCK) {
		return snd_pcm_lib_read_block(subchn, buf, count, nonblock, snd_pcm_lib_read_transfer);
	} else if (runtime->mode == SND_PCM_MODE_STREAM) {
		return snd_pcm_lib_read_stream(subchn, buf, count, nonblock, snd_pcm_lib_read_transfer);
	}
	return -EIO;
}

static int snd_pcm_lib_readv_transfer(snd_pcm_subchn_t *subchn,
				      void *dst, unsigned int dstoff,
				      unsigned int srcoff, unsigned int size)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int err;
	char **bufs = dst;
	int voices = runtime->format.voices;
	int v;
	size /= voices;
	for (v = 0; v < voices; ++v) {
		if (runtime->hw_memcpy) {
			if ((err = runtime->hw_memcpy(subchn, v, srcoff, bufs[v] + dstoff, size)) < 0)
				return err;
		} else {
			if (copy_to_user(bufs[v] + dstoff, runtime->dma_area->buf + srcoff, size))
				return -EFAULT;
			srcoff += size;
		}
		snd_pcm_proc_write(subchn, runtime->position, bufs[v] + dstoff, size, 0);
	}
	return 0;
}
 
long snd_pcm_lib_readv(snd_pcm_subchn_t *subchn, const struct iovec *vector,
			unsigned long vcount)
{
	snd_pcm_runtime_t *runtime;
	int nonblock;
	int voices;
	int b, bcount;
	int result = 0;

	snd_debug_check(subchn == NULL, -ENXIO);
	runtime = subchn->runtime;
	snd_debug_check(runtime == NULL, -ENXIO);
	snd_debug_check(runtime->dma_area == NULL, -EINVAL);
	if (*runtime->status < SND_PCM_STATUS_PREPARED)
		return -EBADFD;

	snd_debug_check(subchn->ffile == NULL, -ENXIO);
	nonblock = !!(subchn->ffile->f_flags & O_NONBLOCK);
#ifdef CONFIG_SND_OSSEMUL
	if (subchn->oss.oss) {
		snd_pcm_oss_setup_t *setup = subchn->oss.setup;
		if (setup != NULL) {
			if (setup->nonblock)
				nonblock = 1;
			else if (setup->block)
				nonblock = 0;
		}
	}
#endif

	if (runtime->format.interleave) {
		for (b = 0; b < vcount; b++) {
			char *buf = vector[b].iov_base;
			size_t len = vector[b].iov_len;
			int ret;
			if (runtime->mode == SND_PCM_MODE_BLOCK)
				ret = snd_pcm_lib_read_block(subchn, buf, len, nonblock, snd_pcm_lib_read_transfer);
			else if (runtime->mode == SND_PCM_MODE_STREAM)
				ret = snd_pcm_lib_read_stream(subchn, buf, len, nonblock, snd_pcm_lib_read_transfer);
			else
				return -EIO;
			if (ret < 0)
				return result > 0 ? result : ret;
			if (ret != len)
				break;
			result += ret;
		}
	} else {
		voices = runtime->format.voices;
		if (vcount % voices)
			return -EINVAL;
		bcount = vcount / voices;
		for (b = 0; b < bcount; ++b) {
			int ret;
			int v;
			int count = 0;
			char *bufs[voices];
			for (v = 0; v < voices; ++v) {
				size_t len = vector[v].iov_len;
				bufs[v] = vector[v].iov_base;
				if (v == 0)
					count = len;
				else if (count != len)
					return -EINVAL;
				if (bufs[v] != NULL && verify_area(VERIFY_WRITE, bufs[v], len))
					return -EFAULT;
			}
			if (runtime->mode == SND_PCM_MODE_BLOCK)
				ret = snd_pcm_lib_read_block(subchn, bufs, count * voices, nonblock, snd_pcm_lib_readv_transfer);
			else if (runtime->mode == SND_PCM_MODE_STREAM)
				ret = snd_pcm_lib_read_stream(subchn, bufs, count * voices, nonblock, snd_pcm_lib_readv_transfer);
			else
				return -EIO;
			if (ret < 0)
				return result > 0 ? result : ret;
			result += ret;
			if (ret != count * voices)
				break;
			vector += voices;
		}
	}
	return result;
}

/*
 *  Exported symbols
 */

EXPORT_SYMBOL(snd_pcm_dma_alloc);
EXPORT_SYMBOL(snd_pcm_dma_setup);
EXPORT_SYMBOL(snd_pcm_dma_free);
EXPORT_SYMBOL(snd_pcm_set_sync);
EXPORT_SYMBOL(snd_pcm_set_mixer);
EXPORT_SYMBOL(snd_pcm_lib_transfer_size);
EXPORT_SYMBOL(snd_pcm_lib_transfer_fragment);
EXPORT_SYMBOL(snd_pcm_lib_interleave_len);
EXPORT_SYMBOL(snd_pcm_lib_set_buffer_size);
EXPORT_SYMBOL(snd_pcm_lib_mmap_ctrl_ptr);
EXPORT_SYMBOL(snd_pcm_lib_ioctl);
EXPORT_SYMBOL(snd_pcm_playback_ok);
EXPORT_SYMBOL(snd_pcm_capture_ok);
EXPORT_SYMBOL(snd_pcm_playback_data);
EXPORT_SYMBOL(snd_pcm_capture_empty);
EXPORT_SYMBOL(snd_pcm_clear_values);
EXPORT_SYMBOL(snd_pcm_transfer_stop);
EXPORT_SYMBOL(snd_pcm_transfer_done);
EXPORT_SYMBOL(snd_pcm_lib_write);
EXPORT_SYMBOL(snd_pcm_lib_read);
