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

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

/*

 */

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

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

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

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

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

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

	if (pchn->buffer == NULL) {
		printf("DMA BUFFER IS NULL\n");
		return;
	}
	size = pchn->used_size;
	if (pchn->hw.dma_neutral) {	/* lowlevel driver does size/voice conversion */
		pchn->hw.dma_neutral(pcm1, pchn->buffer, 0, size, pchn->neutral_byte);
		pchn->flags &= ~SND_PCM1_FLG_NEUTRAL;
		return;
	}
	if (pchn->hw.flags & SND_PCM1_HW_8BITONLY) {
		if (pchn->mode & SND_PCM1_MODE_16)
			size >>= 1;
	}
	if (pchn->hw.flags & SND_PCM1_HW_16BITONLY) {
		if (!(pchn->mode & SND_PCM1_MODE_16))
			size <<= 1;
	}
	if (pchn->hw.flags & SND_PCM1_HW_MONOONLY) {
		if (!(pchn->voices == 1))
			size >>= 1;
	}
	if (pchn->hw.flags & SND_PCM1_HW_STEREOONLY) {
		if (pchn->voices == 1)
			size <<= 1;
	}

#ifdef SNDCFG_DEBUG
	if (size > pchn->size) {
		snd_printd("pcm: Oops, neutral fill size %i > DMA size %i\n", size, pchn->size);
		size = pchn->size;
	}
#endif
	memset(pchn->buffer, pchn->neutral_byte, size);
	pchn->flags &= ~SND_PCM1_FLG_NEUTRAL;
}

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

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

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

 */

unsigned short snd_pcm1_file_flags(snd_ocb_t *ocb)
{
	switch (ocb->iofunc_ocb.ioflag & _IO_FLAG_MASK) {
		case _IO_FLAG_WR:
			return SND_PCM1_LFLG_PLAY;
		case _IO_FLAG_RD:
			return SND_PCM1_LFLG_RECORD;
		case _IO_FLAG_RD|_IO_FLAG_WR:
			return SND_PCM1_LFLG_BOTH;
		default:
			return 0;
	}
}

/*
 *  some shared routines for lowlevel driver
 */

void snd_pcm1_playback_dma(snd_pcm1_t *pcm1,
			   unsigned char *buffer, unsigned int offset,
			   unsigned int count)
{
	snd_copy_from_user(pcm1->playback.ctp, &buffer[offset], count);
}

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

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

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

void snd_pcm1_record_dma(snd_pcm1_t * pcm1,
			 unsigned char *buffer, unsigned int offset,
			 unsigned int count)
{
	snd_copy_to_user(pcm1->record.ctp, &buffer[offset], count);
}

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

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

void snd_pcm1_dma_move(snd_pcm1_t * pcm1,
		       unsigned char *dbuffer, unsigned int dest_offset,
		       unsigned char *sbuffer, unsigned int src_offset,
		       unsigned int count)
{
	memcpy(&dbuffer[dest_offset], &sbuffer[src_offset], count);
}

/*
 *  interrupt callbacks from lowlevel driver
 */

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

	snd_mutex_lock(pchn, lock);
	freeblks = pchn->blocks - pchn->used;
	result = pchn->used <= pchn->blocks_max &&
	    freeblks >= pchn->blocks_room &&
	    !(pchn->flags & SND_PCM1_FLG_PAUSE);
	snd_mutex_unlock(pchn, lock);
	return result;
}

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

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

static void snd_pcm1_interrupt_playback(snd_pcm1_t * pcm1)
{
	snd_pcm1_channel_t *pchn;
	int trigger = 0;
	
	pchn = &pcm1->playback;
	if (!(pchn->flags & SND_PCM1_FLG_TRIGGER)) {
		snd_printk("snd_pcm_interrupt_playback: Oops, playback interrupt when playback isn't active!!!\n");
		return;
	}
	if (pchn->flags & SND_PCM1_FLG_MMAP) {
		snd_mutex_lock(pchn, lock);
		pchn->interrupts++;
		pchn->processed_bytes += pchn->block_size;
		pchn->head++;
		pchn->head %= pchn->blocks;
		snd_mutex_unlock(pchn, lock);
		return;
	} 

	if (pchn->used <= 0) {
		snd_printk("snd_pcm_interrupt_playback: Internal error (1)\n");
		return;
	}
	if (pchn->flags & SND_PCM1_FLG_PAUSE) {
		pchn->hw.trigger(pcm1, 0);
		return;
	}
#if 0
	if (0 && pchn->hw.flags & SND_PCM1_HW_AUTODMA) {
		/* try clear first few bytes from non-active block */
		pchn->hw.dma_neutral(pcm1, pchn->buffer,
				     pchn->tail * pchn->block_size,
				     16, pchn->neutral_byte);
	}
#endif
	snd_mutex_lock(pchn, lock);
	if (pchn->used == 1) {
		if (!(pchn->flags & SND_PCM1_FLG_SYNC)) {
			snd_printk("snd_pcm_interrupt_playback: Driver buffer empty!!!\n");
			pchn->total_xruns++;
			pchn->xruns++;
		}
	}
	pchn->interrupts++;
	if (pchn->used > 0) {
		pchn->used--;
		pchn->processed_bytes += pchn->block_size;
	}
	pchn->tail++;
	pchn->tail %= pchn->blocks;
	if (pchn->used) {
		trigger = 1;
	} 
	snd_mutex_unlock(pchn,lock);	
	if (trigger) {
		if (!(pchn->hw.flags & SND_PCM1_HW_AUTODMA)) {
			pchn->hw.prepare(pcm1,
					 pchn->buffer, pchn->used_size,
					 pchn->tail * pchn->real_block_size,
					 pchn->real_block_size);
			pchn->hw.trigger(pcm1, 1);
		}
	} else {
		if (pchn->hw.flags & SND_PCM1_HW_AUTODMA)
			pchn->hw.trigger(pcm1, 0);
		snd_pcm1_clear_time(pchn);
		pchn->flags &= ~SND_PCM1_FLG_TRIGGER;
	}

	if (snd_pcm1_playback_ok(pchn)) {
        snd_mutex_lock(pchn, lock);
		if (pchn->flags & SND_PCM1_FLG_SLEEP)
			snd_wakeup(pchn, sleep);
		else if (pchn->flags & SND_PCM1_FLG_IONOTIFY) {
			snd_notify_trigger(pchn->ocb, 1, NOTIFY_OUTPUT);
			pchn->flags &= ~SND_PCM1_FLG_IONOTIFY;
		}
        snd_mutex_unlock(pchn, lock);
	}

	if (pchn->timer && (pchn->flags & SND_PCM1_FLG_TIMER)) {
		snd_timer_interrupt(pchn->timer, 1);
	}
}

static void snd_pcm1_interrupt_record(snd_pcm1_t * pcm1)
{
	snd_pcm1_channel_t *pchn;
	int trigger = 0;

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

	if (snd_pcm1_record_ok(pchn)) {
                snd_mutex_lock(pchn, lock);
		if (pchn->flags & SND_PCM1_FLG_SLEEP)
			snd_wakeup(pchn, sleep);
		else if (pchn->flags & SND_PCM1_FLG_IONOTIFY) {
			snd_notify_trigger(pchn->ocb, 1, NOTIFY_INPUT);
			pchn->flags &= ~SND_PCM1_FLG_IONOTIFY;
		}
                snd_mutex_unlock(pchn, lock);
	}

	if (pchn->timer && (pchn->flags & SND_PCM1_FLG_TIMER)) {
		snd_timer_interrupt(pchn->timer, 1);
	}
}

/*
 *  trigger standard buffered playback
 */

static void snd_pcm1_trigger_playback(snd_pcm1_t * pcm1,
				      snd_pcm1_channel_t * pchn)
{
	if (!pchn->used)
		return;
	snd_mutex_lock(pchn, lock);
	if (!(pchn->flags & SND_PCM1_FLG_TRIGGER) &&
	    !(pchn->flags & SND_PCM1_FLG_PAUSE)) {
		pchn->flags |= SND_PCM1_FLG_TRIGGERA;
		snd_mutex_unlock(pchn, lock);
		pchn->lastxruns = pchn->xruns;
		if (pchn->tail != 0)
			snd_printd( "Oops, playback start when tail = %i\n", pchn->tail);
		pchn->hw.prepare(pcm1,
				 pchn->buffer, pchn->used_size,
				 pchn->tail * pchn->real_block_size,
				 pchn->real_block_size);
		pchn->hw.trigger(pcm1, 1);
		if (pchn->flags & SND_PCM1_FLG_TIME) {
			gettimeofday(&pchn->time, NULL);
		}
	} else {
		snd_mutex_unlock(pchn, lock);
	}
}

/* must be called when playback mutex is locked */

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

/*
 *  user to dma
 */

//
//this convert function will /ONLY/ convert 2 channel 16 bit audio.
// You must keep in mind that channels are interlaced going
// left, right, left, right, etc etc
// so its [left word] [right word] [left word] [right word]
//
// so you have to make sure not to cross channels over or you
// will get static.
//
//
// i don't use size1 because i have the buffers set up so that
// size2 is already the same length of time as the first buffer
// but in the seconds speed. For example, I knew that I would
// be converting 44100 to 48000 so I made my buffers 882 bytes
// and 960 bytes. So they would be even conversions.
//
// soco dec 16, 1999
// soco@request.com

void src_convert(   short *src, unsigned int speed1, int size1,
                                short *dest, unsigned int speed2, int size2 )
{
        unsigned long cur=0;    // source
        unsigned long step;
        int x=0;

        step = (((float)speed1)/((float)speed2)) * 65536.0;

        for(x=0; x< size2>>2; x++ )
        {
                dest[ (x<<1) ] = src[ ((cur>>15) &0xFFFE ) ];
                dest[ (x<<1)+1 ] = src[ ((cur>>15) &0xFFFE ) +1 ];
                cur+= step;
        }
}

static int snd_pcm1_user_to_buffer(snd_context_t *ctp, snd_pcm1_t * pcm1, int count)
{
	snd_pcm1_channel_t *pchn;
	int result, tmp;

	if (count <= 0 )
		return 0;

	pchn = &pcm1->playback;
	pchn->ctp = ctp;

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

//	snd_printk("write - %d\n",count);

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

	result = 0;

	while (count > 0) {
		while (!snd_pcm1_playback_ok(pchn)) {
			if (!(pchn->flags & SND_PCM1_FLG_TRIGGER))
				snd_pcm1_trigger_playback(pcm1, pchn);
			if (ctp->nonblock)
				return result;
			if (pchn->flags & SND_PCM1_FLG_PAUSE)
				return result;
			if (!(pchn->flags & SND_PCM1_FLG_ENABLE))
				return result;
			snd_mutex_lock(pchn, lock);
			pchn->flags |= SND_PCM1_FLG_SLEEP;
			snd_sleep(pchn, sleep, lock, 30);
			pchn->flags &= ~SND_PCM1_FLG_SLEEP;
			snd_mutex_unlock(pchn, lock);
			if (snd_sleep_abort(pchn, sleep)) {
				return !result ? -EINTR : result;
			}
			if (pchn->used >= pchn->blocks && snd_timeout(pchn, sleep)) {
				snd_printk("pcm_user_to_dma: timeout, new block discarded\n");
				return -EIO;
			}
		}
		/* ok.. now we have right block - fill it */
		snd_mutex_lock(pchn, lock);
		tmp = pchn->block_size - pchn->frag_size;
		if (tmp > count)
			tmp = count;	/* correction */
		if (pchn->lastxruns != pchn->xruns)
			snd_pcm1_playback_underrun(pcm1, pchn);
		snd_mutex_unlock(pchn, lock);

		if ((pchn->rate != pchn->real_rate)
		&&  (pchn->hw.flags & SND_PCM1_HW_NO_SRC)) {
			pchn->hw.dma(pcm1, pchn->in_buffer, pchn->frag_size, tmp);
		} else
			pchn->hw.dma(pcm1, pchn->buffer, (pchn->head * pchn->real_block_size) + pchn->frag_size, tmp);

//		snd_pcm_proc_write(&pcm1->pcm->playback, buf, tmp);

		count -= tmp;
		result += tmp;
		
		snd_mutex_lock(pchn, lock);
		pchn->frag_size += tmp;
		if (pchn->lastxruns != pchn->xruns)	/* oops, underrun */
			snd_pcm1_playback_underrun(pcm1, pchn);
		while (pchn->frag_size >= pchn->block_size) {

			if ((pchn->rate != pchn->real_rate)
			&&  (pchn->hw.flags & SND_PCM1_HW_NO_SRC)) {
				if (pchn->frag_size >= pchn->block_size ) {
					src_convert( pchn->in_buffer, pchn->rate, pchn->block_size, 
					        	 pchn->buffer + (pchn->head * pchn->real_block_size), 
						                          pchn->real_rate, pchn->real_block_size );
				}
				// TODO: replace this with a circular buffer
				memmove( pchn->in_buffer, pchn->in_buffer + pchn->block_size, 
								pchn->frag_size - pchn->block_size );
			}
	
			pchn->used++;
			pchn->head++;
			pchn->head %= pchn->blocks;
			pchn->frag_size -= pchn->block_size;
		}
		snd_mutex_unlock(pchn, lock);
	}
	if (pchn->used)
		snd_pcm1_trigger_playback(pcm1, pchn);
	return result;
}

/*
 *  trigger standard buffered record
 */

static void snd_pcm1_trigger_record(snd_pcm1_t * pcm1, snd_pcm1_channel_t * pchn)
{
	if (pchn->used >= pchn->blocks)
		return;
	snd_mutex_lock(pchn, lock);
	pchn->flags &= ~SND_PCM1_FLG_NEEDEMPTY;
	if (!(pchn->flags & SND_PCM1_FLG_TRIGGER)) {
		pchn->flags |= SND_PCM1_FLG_TRIGGERA;
		snd_pcm1_lock(pchn, pchn->head);
		snd_mutex_unlock(pchn, lock);
		pchn->lastxruns = pchn->xruns;
		if (!pchn->used)
			pchn->head = pchn->tail = 0;
		if (pchn->head != 0)
			snd_printd( "Oops, record start when head = %i\n", pchn->head);
		pchn->hw.prepare(pcm1,
				 pchn->buffer, pchn->used_size,
				 pchn->head * pchn->real_block_size,
				 pchn->real_block_size);
		pchn->hw.trigger(pcm1, 1);
		if (pchn->flags & SND_PCM1_FLG_TIME) {
			gettimeofday(&pchn->time, NULL);
		}
	} else {
		snd_mutex_unlock(pchn, lock);
	}
}

/*
 *  dma to user
 */

static int snd_pcm1_buffer_to_user(snd_context_t *ctp, snd_pcm1_t * pcm1, int count)
{
	snd_pcm1_channel_t *pchn;
	int result, tmp;

	if (count <= 0)
		return 0;

	pchn = &pcm1->record;
	pchn->ctp = ctp;

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

//	snd_printk("read - %d\n",count);

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

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

	while (count > 0) {
		while (!snd_pcm1_record_ok(pchn)) {
			snd_pcm1_trigger_record(pcm1, pchn);
			if (ctp->nonblock)
				return result;
			snd_mutex_lock(pchn, lock);
			pchn->flags |= SND_PCM1_FLG_SLEEP;
			snd_sleep(pchn, sleep, lock, 30);
			pchn->flags &= ~SND_PCM1_FLG_SLEEP;
			snd_mutex_unlock(pchn, lock);
			if (snd_sleep_abort(pchn, sleep)) {
				return !result ? -EINTR : result;
			}
			if (!pchn->used && snd_timeout(pchn, sleep) ) {
				snd_printd("snd_pcm_dma_to_user: data timeout\n");
				return -EIO;
			}
		}
		snd_mutex_lock(pchn, lock);
		tmp = count <= pchn->frag_size ? count : pchn->frag_size;
		snd_mutex_unlock(pchn, lock);

		pchn->hw.dma(pcm1, pchn->buffer, (pchn->tail * pchn->block_size) + (pchn->block_size - pchn->frag_size), tmp);

//		snd_pcm_proc_write(&pcm1->pcm->record, buf, tmp);
		count -= tmp;
		result += tmp;

		snd_mutex_lock(pchn, lock);
		pchn->frag_size -= tmp;
		if (pchn->lastxruns != pchn->xruns) {	/* oops, overrun */
			pchn->flags |= SND_PCM1_FLG_NEEDEMPTY;
			pchn->lastxruns = pchn->xruns;
		}
		if (!pchn->frag_size) {
			pchn->used--;
			pchn->tail++;
			pchn->tail %= pchn->blocks;
			pchn->frag_size = pchn->block_size;
		}
		if ((pchn->flags & SND_PCM1_FLG_NEEDEMPTY) && !pchn->used) {
			pchn->head = pchn->tail = 0;
		}
		snd_mutex_unlock(pchn, lock);
	}
//snd_printk("read - %d\n",result);

	return result;
}

/*
 *  synchronize playback
 */

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

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

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

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

		unsigned int size, count, offset;

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

	while (pchn->used) {
		snd_mutex_lock(pchn, lock);
		pchn->flags |= SND_PCM1_FLG_SLEEP;
		snd_sleep(pchn, sleep, lock, 30);
		pchn->flags &= ~SND_PCM1_FLG_SLEEP;
		snd_mutex_unlock(pchn, lock);
		if (snd_sleep_abort(pchn, sleep)) {
			err = -EINTR;
			break;
		}
		if (pchn->used && snd_timeout(pchn, sleep) ) {
			snd_printd("snd_pcm1_flush_playback: timeout, skipping waiting blocks\n");
			err = -EAGAIN;
			break;
		}
	}
	pchn->flags &= ~SND_PCM1_FLG_SYNC;
	if (close_flag || (err != -EINTR && err != -EAGAIN))
		snd_pcm1_drain_playback(pcm1);
	return err;
}



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

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

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

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


/*
 *  other things
 */

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

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

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

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

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

	pcm = pcm1->pcm;
	memset(&ginfo, 0, sizeof(ginfo));
	ginfo.type = pcm1->card->type;
	ginfo.flags = pcm->info_flags;
	strncpy(ginfo.id, pcm->id, sizeof(ginfo.id) - 1);
	strncpy(ginfo.name, pcm->name, sizeof(ginfo.name) - 1);
	memcpy( xinfo, &ginfo, sizeof(ginfo));
	return sizeof( snd_pcm_info_t );
}

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

	memset(&ginfo, 0, sizeof(ginfo));
	pchn = &pcm1->playback;
	ginfo.flags = pchn->hw.flags & SND_PCM1_HW_COPYMASK;
	ginfo.formats = pchn->hw.formats;
	ginfo.min_rate = pchn->hw.min_rate;
	ginfo.max_rate = pchn->hw.max_rate;
	ginfo.min_channels = 1;
	ginfo.max_channels = pchn->hw.max_voices;
	ginfo.buffer_size = pchn->size;


	if (pchn->hw.flags & SND_PCM1_HW_8BITONLY) {
		if (pchn->mode & SND_PCM1_MODE_16)
			ginfo.buffer_size <<= 1;
	}
	if (pchn->hw.flags & SND_PCM1_HW_16BITONLY) {
		if (!(pchn->mode & SND_PCM1_MODE_16))
			ginfo.buffer_size >>= 1;
	}
	if (pchn->hw.flags & SND_PCM1_HW_MONOONLY) {
		if (!(pchn->voices == 1))
			ginfo.buffer_size <<= 1;
	}
	if (pchn->hw.flags & SND_PCM1_HW_STEREOONLY) {
		if (pchn->voices == 1)
			ginfo.buffer_size >>= 1;
	}

	ginfo.min_fragment_size = 1;
	for (i = 1; i < pchn->hw.min_fragment; i++)
		ginfo.min_fragment_size <<= 1;
	ginfo.max_fragment_size = ginfo.buffer_size / 2;
	ginfo.fragment_align = pchn->hw.align;
	ginfo.hw_formats = pchn->hw.hw_formats;
	ginfo.switches = snd_switch_count(&pcm1->pcm->playback.switches);
	memcpy(xinfo, &ginfo, sizeof(ginfo));
	return sizeof( snd_pcm_playback_info_t );
}

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

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

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

	memcpy(&format, _format, sizeof(format));

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

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

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

	memcpy(&params, _params, sizeof(params));
	if ((tmp = snd_pcm1_flush_playback(pcm1, 0)) < 0)
		return tmp;
	pchn = &pcm1->playback;
	snd_mutex_lock(pchn, lock);
	for (tmp = i = 1; i < pchn->hw.min_fragment; i++)
		tmp <<= 1;
	if (params.fragment_size < tmp ||
	    params.fragment_size > pchn->size / 2) {
		snd_mutex_unlock(pchn, lock);
		return -EINVAL;
	}

	if (pchn->in_buffer)
		snd_free(pchn->in_buffer, pchn->block_size << 2);

	pchn->used_size = pchn->size;
        pchn->block_size = snd_pcm1_align(pcm1, SND_PCM1_PLAYBACK, params.fragment_size);
        pchn->real_block_size = pchn->block_size;

	if (pchn->hw.flags & SND_PCM1_HW_8BITONLY) {
		if (pchn->mode & SND_PCM1_MODE_16) {
			pchn->used_size <<= 1;
		}
	}
	if (pchn->hw.flags & SND_PCM1_HW_16BITONLY) {
		if (!(pchn->mode & SND_PCM1_MODE_16)) {
			pchn->used_size >>= 1;
		}
	}
	if (pchn->hw.flags & SND_PCM1_HW_MONOONLY) {
		if (!(pchn->voices == 1)) {
			pchn->used_size <<= 1;
		}
	}
	if (pchn->hw.flags & SND_PCM1_HW_STEREOONLY) {
		if (pchn->voices == 1) {
			pchn->used_size >>= 1;
		}
	}


        if ((pchn->rate != pchn->real_rate)
        &&  (pchn->hw.flags & SND_PCM1_HW_NO_SRC)) {

                pchn->real_block_size /= 480;
		pchn->real_block_size &= ~0x3; // align with 4 (16-bit, stereo only)
                pchn->block_size = pchn->real_block_size * 441;
                pchn->real_block_size *= 480;

		pchn->in_buffer = snd_malloc(pchn->block_size << 2);

        }
        pchn->blocks = pchn->used_size / pchn->real_block_size;
        pchn->used_size = pchn->blocks * pchn->real_block_size;

	if (params.fragments_max < 0) {
		pchn->blocks_max = pchn->blocks + params.fragments_max;
		if (pchn->blocks_max < 1)
			pchn->blocks_max = 1;
	} else {
		pchn->blocks_max = params.fragments_max;
		if (pchn->blocks_max > pchn->blocks - 1)
			pchn->blocks_max = pchn->blocks - 1;
	}
	pchn->blocks_room = params.fragments_room;
	if (pchn->blocks_room > pchn->blocks / 2)
		pchn->blocks_room = pchn->blocks / 2;
//	snd_pcm1_new_timer_resolution(pchn);
	snd_mutex_unlock(pchn, lock);
#ifdef SND_PCM1_DEBUG_BUFFERS
	snd_printd("playback_params: used_size = %i, blocks = %i, blocks_size = %i, blocks_max = %i, blocks_room = %i\n", pchn->used_size, pchn->blocks, pchn->block_size, pchn->blocks_max, pchn->blocks_room);
#endif
	return 0;
}

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

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

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

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

static int snd_pcm1_playback_status(snd_pcm1_t * pcm1,
				    snd_pcm_playback_status_t * _status)
{
	snd_pcm_playback_status_t status;
	snd_pcm1_channel_t *pchn;
	int ptr;

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

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

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

static int snd_pcm1_reset_time( snd_pcm1_t *pcm1, snd_pcm1_channel_t *pchn, struct timeval *time )
{
	int err = 0,
	    bps,
	    ptr;

        /* mutex lock block */
        snd_mutex_lock(pchn, lock);
        if ((pchn->flags & SND_PCM1_FLG_TRIGGER) ||
            ((pchn->flags & SND_PCM1_FLG_PAUSE) &&
             (pchn->hw.flags & SND_PCM1_HW_PAUSE))) {
                if (pchn->hw.flags & SND_PCM1_HW_BLOCKPTR) {
                        ptr = pchn->hw.pointer(pcm1, pchn->block_size);
                } else {
                        ptr = pchn->hw.pointer(pcm1, pchn->used_size);
                        ptr -= pchn->tail * pchn->block_size;
                }
                if (ptr < 0)
                        ptr = 0;
                if (ptr > pchn->block_size - 1)
                        ptr = pchn->block_size - 1;
        } else {
                ptr = 0;
        }

	if( !time || ( time->tv_sec == 0 && time->tv_usec == 0 )) {
		memset( &pchn->time, 0, sizeof(struct timeval));  
		pchn->processed_bytes = -ptr;
	} else {
		if( time->tv_usec >= 1000000 ) {
			time->tv_sec = time->tv_usec / 1000000;
			time->tv_usec = time->tv_usec % 1000000;
		}

		memcpy( &pchn->time, time, sizeof(struct timeval));

		bps = snd_pcm1_bytes_per_second(pchn);

		pchn->processed_bytes   = ( bps * time->tv_sec )
					+ ( (double) bps / (double) 1000000.0 * time->tv_usec )
					- ( ptr );
	}
        snd_mutex_unlock(pchn, lock);

	return err;
}

static int snd_pcm1_reset_playback_time( snd_pcm1_t *pcm1, struct timeval *time )
{
	return snd_pcm1_reset_time( pcm1, &pcm1->playback, time );
}

static int snd_pcm1_reset_record_time( snd_pcm1_t *pcm1, struct timeval *time )
{
	return snd_pcm1_reset_time( pcm1, &pcm1->record, time );
}

static int snd_pcm1_settrigger( snd_pcm1_t *pcm1, int set)
{
	int err = EOK;
	snd_pcm1_channel_t *pchn = NULL;

	if (set == SND_PCM_DISABLE) {
		pchn = &pcm1->playback;
		pchn->flags &= ~SND_PCM1_FLG_TRIGGER;
		pchn->hw.trigger (pcm1, 0);

		pchn = &pcm1->record;
		pchn->flags &= ~SND_PCM1_FLG_TRIGGER;
		pchn->hw.trigger (pcm1, 0);
	} else {
		if (set & SND_PCM_ENABLE_OUTPUT) {
			pchn = &pcm1->playback;
			pchn->flags |= SND_PCM1_FLG_TRIGGER|SND_PCM1_FLG_MMAP;
			pchn->hw.prepare(pcm1,
				pchn->buffer, pchn->used_size,
				pchn->tail * pchn->real_block_size,
				pchn->real_block_size);
			pchn->hw.trigger (pcm1, 1);
		}
		if (set & SND_PCM_ENABLE_INPUT) {
			pchn = &pcm1->record;
			pchn->flags |= SND_PCM1_FLG_TRIGGER|SND_PCM1_FLG_MMAP;
			pchn->hw.prepare(pcm1,
				pchn->buffer, pchn->used_size,
				pchn->tail * pchn->real_block_size,
				pchn->real_block_size);
			pchn->hw.trigger (pcm1, 1);
		}
	}
	return err; 
}

static int snd_pcm1_shared_memory( snd_pcm1_t *pcm1, snd_pcm_shared_mem_t* shm )
{
	int err = EOK;
	snd_pcm1_channel_t *pchn = NULL;

	if (shm->channel == 0) {
		pchn = &pcm1->playback;
	} else {
		pchn = &pcm1->record;
	}

	memset( shm->name, 0, sizeof(shm->name));
	if ((pchn->dmaptr) || (pchn->dmaptr->shm_name)) {
		strcpy( shm->name, pchn->dmaptr->shm_name );		
	}

	return err;
}

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

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

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

	pchn = &pcm1->playback;

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

static int snd_pcm1_open_card(snd_pcm1_t * pcm1, snd_ocb_t *ocb)
{
	unsigned short flags;
	int res;
	snd_pcm_t *pcm;
	snd_pcm_format_t format;
	snd_pcm_playback_params_t pparams;
	snd_pcm_record_params_t rparams;

	flags = snd_pcm1_file_flags(ocb);

	snd_printd("snd_pcm1_open_card (1) - pcm1 = 0x%lx, flags = 0x%x, pcm->flags = 0x%x\n", (long) pcm1, flags, pcm1->flags);

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

static void snd_pcm1_close_card(snd_pcm1_t * pcm1, snd_ocb_t *ocb)
{
	unsigned short flags;

	if (!pcm1)
		return;

	flags = snd_pcm1_file_flags(ocb) & pcm1->flags;

	if (flags & SND_PCM1_LFLG_PLAY && pcm1->playback.ocb == ocb ) {
		snd_pcm1_flush_playback(pcm1, 1);	/* synchronize playback */
		pcm1->playback.hw.trigger(pcm1, 0);	/* for sure */
		pcm1->playback.hw.close(pcm1);
		pcm1->flags &= ~SND_PCM1_LFLG_PLAY;
	}
	if (flags & SND_PCM1_LFLG_RECORD && pcm1->record.ocb == ocb ) {
		snd_pcm1_drain_record(pcm1);	/* and record, data can't be read, so flush isn't needed */
		pcm1->record.hw.trigger(pcm1, 0);	/* for sure */
		pcm1->record.hw.close(pcm1);
		pcm1->flags &= ~SND_PCM1_LFLG_RECORD;
	}
}

static snd_open_t snd_pcm1_open;
static int snd_pcm1_open(void *data, snd_ocb_t *ocb)
{
	snd_pcm_t *pcm = (snd_pcm_t *)data;
	snd_pcm1_t *pcm1;
	int res;
	
	if (!pcm)
		return -ENODEV;

	pcm1 = (snd_pcm1_t *) pcm->private_data;
	if (!pcm1)
		return -ENODEV;

	snd_mutex_lock(pcm1, open);
	if ((res = snd_pcm1_open_card(pcm1, ocb)) < 0) {
		snd_mutex_unlock(pcm1, open);
		return res;
	}
	ocb->private_data = pcm1;
	snd_mutex_unlock(pcm1, open);	
	return -EOK;
}

static snd_close_t snd_pcm1_close;
static int snd_pcm1_close(snd_ocb_t *ocb)
{
	snd_pcm1_t *pcm1;

	if (ocb->private_data) {
		pcm1 = (snd_pcm1_t *) ocb->private_data;
		snd_pcm1_close_card(pcm1, ocb);
	}
	return EOK;
}

static snd_ioctl_t snd_pcm1_ioctl;
static int snd_pcm1_ioctl(snd_ocb_t *ocb, unsigned int cmd, unsigned long arg, int len)
{
	snd_pcm1_t *pcm1;
	snd_card_t *card;
	unsigned short flags;
	int tmp;

	pcm1 = (snd_pcm1_t *) ocb->private_data;
	card = pcm1->card;
	if (((cmd >> 8) & 0xff) != 'A')
		return -ENOSYS;
	flags = snd_pcm1_file_flags(ocb);
#if 0
	snd_printd("cmd = 0x%x, arg = 0x%lx, flags = 0x%x\n", cmd, *(long *) arg, flags);
#endif
	switch (cmd) {
	case SND_PCM_IOCTL_PVERSION:
		return snd_ioctl_out((long *) arg, SND_PCM_VERSION);
	case SND_PCM_IOCTL_INFO:
		return snd_pcm1_info(pcm1, (snd_pcm_info_t *) arg);
	case SND_PCM_IOCTL_PLAYBACK_INFO:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		return snd_pcm1_playback_info(pcm1, (snd_pcm_playback_info_t *) arg);
	case SND_PCM_IOCTL_RECORD_INFO:
		if (!(flags & SND_PCM1_LFLG_RECORD))
			return -EIO;
		return snd_pcm1_record_info(pcm1, (snd_pcm_record_info_t *) arg);
	case SND_PCM_IOCTL_PSWITCHES:
	case SND_PCM_IOCTL_PSWITCH_READ:
	case SND_PCM_IOCTL_PSWITCH_WRITE:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		return snd_pcm_ioctl(pcm1->pcm, cmd, arg);
	case SND_PCM_IOCTL_RSWITCHES:
	case SND_PCM_IOCTL_RSWITCH_READ:
	case SND_PCM_IOCTL_RSWITCH_WRITE:
		if (!(flags & SND_PCM1_LFLG_RECORD))
			return -EIO;
		return snd_pcm_ioctl(pcm1->pcm, cmd, arg);
	case SND_PCM_IOCTL_PLAYBACK_FORMAT:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		if ((tmp = snd_pcm1_format(pcm1, SND_PCM1_PLAYBACK, (snd_pcm_format_t *) arg, 0)) >= 0) {
			return sizeof( snd_pcm_format_t );
		}
		return tmp;
	case SND_PCM_IOCTL_RECORD_FORMAT:
		if (!(flags & SND_PCM1_LFLG_RECORD))
			return -EIO;
		if ((tmp = snd_pcm1_format(pcm1, SND_PCM1_RECORD, (snd_pcm_format_t *) arg, 0)) >= 0) {
			return sizeof( snd_pcm_format_t );
		}
		return tmp;
	case SND_PCM_IOCTL_PLAYBACK_PARAMS:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		if ((tmp = snd_pcm1_playback_params(pcm1, (snd_pcm_playback_params_t *) arg)) >= 0 ) 
			return sizeof( snd_pcm_playback_params_t );
		return tmp;
	case SND_PCM_IOCTL_RECORD_PARAMS:
		if (!(flags & SND_PCM1_LFLG_RECORD))
			return -EIO;
		if (( tmp = snd_pcm1_record_params(pcm1, (snd_pcm_record_params_t *) arg)) >= 0 )
			return sizeof( snd_pcm_record_params_t );
		return tmp;
	case SND_PCM_IOCTL_PLAYBACK_STATUS:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		return snd_pcm1_playback_status(pcm1, (snd_pcm_playback_status_t *) arg);
	case SND_PCM_IOCTL_RECORD_STATUS:
		if (!(flags & SND_PCM1_LFLG_RECORD))
			return -EIO;
		return snd_pcm1_record_status(pcm1, (snd_pcm_record_status_t *) arg);
	case SND_PCM_IOCTL_DRAIN_PLAYBACK:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		return snd_pcm1_drain_playback(pcm1);
	case SND_PCM_IOCTL_FLUSH_PLAYBACK:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		return snd_pcm1_flush_playback(pcm1, 0);
	case SND_PCM_IOCTL_FLUSH_RECORD:
		if (!(flags & SND_PCM1_LFLG_RECORD))
			return -EIO;
		return snd_pcm1_flush_record(pcm1);
	case SND_PCM_IOCTL_PLAYBACK_PAUSE:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		return snd_pcm1_playback_pause(pcm1, *(long *)arg);
	case SND_PCM_IOCTL_PLAYBACK_TIME:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		tmp = *(long *)arg;
		if (tmp)
			pcm1->playback.flags |= SND_PCM1_FLG_TIME;
		else
			pcm1->playback.flags &= ~SND_PCM1_FLG_TIME;
		*(long *) arg = (tmp != 0);
		return sizeof( long);
	case SND_PCM_IOCTL_RECORD_TIME:
		if (!(flags & SND_PCM1_LFLG_RECORD))
			return -EIO;
		tmp = *(long *) arg;
		if (tmp)
			pcm1->record.flags |= SND_PCM1_FLG_TIME;
		else
			pcm1->record.flags &= ~SND_PCM1_FLG_TIME;
		*(long *) arg = (tmp != 0);
		return sizeof(long);

	case SND_PCM_IOCTL_RESET_PLAYBACK_TIME:
		if (!(flags & SND_PCM1_LFLG_PLAY))
			return -EIO;
		return snd_pcm1_reset_playback_time(pcm1, (struct timeval*)arg);
	case SND_PCM_IOCTL_RESET_RECORD_TIME:
		if (!(flags & SND_PCM1_LFLG_RECORD))
			return -EIO;
		return snd_pcm1_reset_record_time(pcm1, (struct timeval*)arg);
	case SND_PCM_IOCTL_SETTRIGGER:
		tmp = *(int *) arg;
		return snd_pcm1_settrigger(pcm1, (int)tmp);
	case SND_PCM_IOCTL_SHARED_MEMORY:

		if ((tmp = snd_pcm1_shared_memory(pcm1, (snd_pcm_shared_mem_t*)arg)) >= 0) {
			return sizeof(snd_pcm_shared_mem_t);
		}
		return tmp;
	default:
		snd_printd("pcm: unknown command = 0x%x\n", cmd);
	}
	return -EIO;
}

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

	tmp = card->number << 2;

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

static snd_read_t snd_pcm1_read;
static long snd_pcm1_read(snd_context_t *ctp, long count)
{
	snd_pcm1_t *pcm1;
	
	pcm1 = (snd_pcm1_t *) ctp->ocb->private_data;
	if (!pcm1 || !(snd_pcm1_file_flags(ctp->ocb) & SND_PCM1_LFLG_RECORD))
		return -EIO;
	
	if (!(pcm1->flags & SND_PCM1_LFLG_RECORD))
		return -EIO;
	return snd_pcm1_buffer_to_user(ctp, pcm1, count);
}

static snd_read_t snd_pcm1_write;
static long snd_pcm1_write(snd_context_t *ctp, long count)
{
	snd_pcm1_t *pcm1;
	
	pcm1 = (snd_pcm1_t *) ctp->ocb->private_data;
	if (!pcm1 || !(snd_pcm1_file_flags(ctp->ocb) & SND_PCM1_LFLG_PLAY))
	   	return -EIO;

   if (!(pcm1->flags & SND_PCM1_LFLG_PLAY))
		return -EIO;
	return snd_pcm1_user_to_buffer(ctp, pcm1, count);
}

static snd_ionotify_t snd_pcm1_ionotify;
static int snd_pcm1_ionotify(snd_ocb_t *ocb, int *trig)
{
	snd_pcm1_t *pcm1;
	snd_pcm1_channel_t *pchn;
	unsigned short fflags;

	fflags = snd_pcm1_file_flags(ocb);
	pcm1 = (snd_pcm1_t *) ocb->private_data;
	if (!pcm1)
		return -EIO;
	if (fflags & SND_PCM1_LFLG_RECORD) {
		pchn = &pcm1->record;
		snd_pcm1_trigger_record(pcm1, pchn);

		if (!snd_pcm1_record_ok(pchn)) {
                        snd_mutex_lock(pchn, lock);
			pchn->flags |= SND_PCM1_FLG_IONOTIFY;
                        snd_mutex_unlock(pchn, lock);
		} else {
                        snd_mutex_lock(pchn, lock);
			pchn->flags &= ~SND_PCM1_FLG_IONOTIFY;
			*trig |= NOTIFY_INPUT;
                        snd_mutex_unlock(pchn, lock);
		}

	}
	if (fflags & SND_PCM1_LFLG_PLAY) {
		pchn = &pcm1->playback;

		if (!snd_pcm1_playback_ok(pchn)) {
                        snd_mutex_lock(pchn, lock);
			pchn->flags |= SND_PCM1_FLG_IONOTIFY;
                        snd_mutex_unlock(pchn, lock);
		} else {
                        snd_mutex_lock(pchn, lock);
			pchn->flags &= ~SND_PCM1_FLG_IONOTIFY;
			*trig |= NOTIFY_OUTPUT;
                        snd_mutex_unlock(pchn, lock);
		}

	}
	return 0;
}

/*
 *  /proc interface
 */

void snd_pcm1_proc_format(snd_pcm_channel_t *pchn, snd_pcm1_channel_t *pchn1)
{
	snd_pcm_format_t format;
	unsigned int mode;
	
	memset(&format, 0, sizeof(format));
	mode = pchn1->mode;
	if (mode & SND_PCM1_MODE_GSM) {
		format.format = SND_PCM_SFMT_GSM;
	} else if (mode & SND_PCM1_MODE_MPEG) {
		format.format = SND_PCM_SFMT_MPEG;
	} else if (mode & SND_PCM1_MODE_ADPCM) {
		format.format = SND_PCM_SFMT_IMA_ADPCM;
	} else if (mode & SND_PCM1_MODE_ALAW) {
		format.format = SND_PCM_SFMT_A_LAW;
	} else if (mode & SND_PCM1_MODE_ULAW) {
		format.format = SND_PCM_SFMT_MU_LAW;
	} else if (mode & SND_PCM1_MODE_U) {
		if (mode & SND_PCM1_MODE_16) {
			format.format = mode & SND_PCM1_MODE_BIG ?
						SND_PCM_SFMT_U16_BE :
						SND_PCM_SFMT_U16_LE;
		} else {
			format.format = SND_PCM_SFMT_U8;
		}
	} else {
		if (mode & SND_PCM1_MODE_16) {
			format.format = mode & SND_PCM1_MODE_BIG ?
						SND_PCM_SFMT_S16_BE :
						SND_PCM_SFMT_S16_LE;
		} else {
			format.format = mode & SND_PCM_SFMT_S8;
		}
	}
	format.rate = pchn1->rate;
	format.channels = pchn1->voices;
	snd_pcm_proc_format(pchn, &format);
}

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

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

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

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

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

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

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

static void snd_pcm1_timer_start(snd_timer_t * timer)
{
	snd_pcm1_channel_t * pchn;
	
	pchn = (snd_pcm1_channel_t *)timer->private_data;
	snd_mutex_lock(pchn, lock);
	pchn->flags |= SND_PCM1_FLG_TIMER;
	snd_mutex_unlock(pchn, lock);
}

static void snd_pcm1_timer_stop(snd_timer_t * timer)
{
	snd_pcm1_channel_t * pchn;
	
	pchn = (snd_pcm1_channel_t *)timer->private_data;
	snd_mutex_lock(pchn, lock);
	pchn->flags &= ~SND_PCM1_FLG_TIMER;
	snd_mutex_unlock(pchn, lock);
}

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

static snd_io_t snd_pcm1_reg =
{
	"digital audio",

	snd_pcm1_read,		/* read */
	snd_pcm1_write,		/* write */
	snd_pcm1_open,		/* open */
	snd_pcm1_close,		/* close */
	snd_pcm1_ionotify,	/* notify */
	snd_pcm1_ioctl,		/* ioctl */
};

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

	pcm1 = (snd_pcm1_t *) data;
	if (pcm1->playback.hw.private_free)
		pcm1->playback.hw.private_free(pcm1->playback.hw.private_data);
	if (pcm1->record.hw.private_free)
		pcm1->record.hw.private_free(pcm1->record.hw.private_data);
	if (pcm1->private_free)
		pcm1->private_free(pcm1->private_data);
	pchn = &pcm1->playback;
	snd_free( pchn->in_buffer, pchn->block_size << 2);
	snd_sleep_destroy(pchn, sleep);
	snd_mutex_destroy(pchn, lock);
	pchn = &pcm1->record;
	snd_free( pchn->in_buffer, pchn->block_size << 2);
	snd_sleep_destroy(pchn, sleep);
	snd_mutex_destroy(pchn, lock);
	snd_free(pcm1, sizeof(snd_pcm1_t));
}

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

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

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

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

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

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

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

/*
 *  ENTRY functions
 */

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

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

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