/*
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *  Routines for control of 8-bit SoundBlaster cards and clones
 *  Code needs to be debugged... I don't have old SB soundcards.
 *
 *
 *   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 <stdio.h>
#include <errno.h>
#include <hw/inout.h>

#include "driver.h"
#include "sb.h"
#include "dma.h"
#include "ulaw.h"

static inline void snd_sb8_ack_8bit(sbdsp_t * codec)
{
	in8(SBP(codec, DATA_AVAIL));
}

static int snd_sb8_compute_rate(snd_pcm1_t * pcm1, int direction)
{
	sbdsp_t *codec;
	snd_pcm1_channel_t *pchn;
	int rate;

	pchn = direction == SND_PCM1_PLAYBACK ?
					&pcm1->playback : &pcm1->record;
	codec = (sbdsp_t *) pcm1->private_data;
	pchn->real_rate = rate = pchn->rate;
	switch (codec->hardware) {
	case SB_HW_10:
	case SB_HW_20:
		if (direction == SND_PCM1_RECORD && rate > 13000)
			return -EINVAL;
		if (rate < 4000)
			return -EINVAL;
		if (rate > 23000)
			return -EINVAL;
		codec->speed8 = (65535 - (256000000 + rate / 2) / rate) >> 8;
		if (direction == SND_PCM1_PLAYBACK) {
			codec->fmt8 = SB_DSP_LO_OUTPUT_AUTO;	/* not used with SB 1.0 */
		} else {
			codec->fmt8 = SB_DSP_LO_INPUT_AUTO;	/* not used with SB 1.0 */
		}
		pchn->real_rate = 256000000 / (65536 - (codec->speed8 << 8));
		break;
	case SB_HW_201:
		if (direction == SND_PCM1_RECORD && rate > 15000)
			return -EINVAL;
		if (rate < 4000)
			return -EINVAL;
		if (rate > 44100)
			return -EINVAL;
		codec->speed8 = (65536 - (256000000 + rate / 2) / rate) >> 8;
		if (direction == SND_PCM1_PLAYBACK) {
			codec->fmt8 = rate > 23000 ? SB_DSP_HI_OUTPUT_AUTO : SB_DSP_LO_OUTPUT_AUTO;
		} else {
			codec->fmt8 = rate > 13000 ? SB_DSP_HI_INPUT_AUTO : SB_DSP_LO_INPUT_AUTO;
		}
		pchn->real_rate = 256000000 / (65536 - (codec->speed8 << 8));
		break;
	case SB_HW_PRO:
		if (rate < 4000)
			rate = 4000;
		if (direction == SND_PCM1_RECORD) {
			if (rate > 15000)
				rate = 15000;
		}
		if (pchn->voices > 1) {
			rate = rate > (22050 + 11025) / 2 ? 22050 : 11025;
			codec->speed8 = (65536 - (256000000 + rate) / (rate << 1)) >> 8;
			if (direction == SND_PCM1_PLAYBACK) {
				codec->fmt8 = SB_DSP_HI_OUTPUT_AUTO;
			} else {
				codec->fmt8 = SB_DSP_HI_INPUT_AUTO;
			}
		} else {
			if (rate > 44100)
				rate = 44100;
			codec->speed8 = (65536 - (256000000 + rate / 2) / rate) >> 8;
			if (direction == SND_PCM1_PLAYBACK) {
				codec->fmt8 = rate > 23000 ? SB_DSP_HI_OUTPUT_AUTO : SB_DSP_LO_OUTPUT_AUTO;
			} else {
				codec->fmt8 = rate > 23000 ? SB_DSP_HI_INPUT_AUTO : SB_DSP_LO_INPUT_AUTO;
			}
		}
		pchn->real_rate = (256000000 / (65536 - (codec->speed8 << 8))) / pchn->voices;
#if 0
		snd_printd("snd_sb8_compute_rate: rate = %u, pchn->real_rate = %u\n", rate, pchn->real_rate);
#endif
		break;
	default:
		snd_printd("unknown hardware for sb8 compute rate!!!\n");
	}
	return 0;
}

static int snd_sb8_playback_ioctl(snd_pcm1_t * pcm1,
				  unsigned int cmd, unsigned long *arg)
{
	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		return snd_sb8_compute_rate(pcm1, SND_PCM1_PLAYBACK);
	}
	return -ENXIO;
}

static int snd_sb8_record_ioctl(snd_pcm1_t * pcm1,
				unsigned int cmd, unsigned long *arg)
{
	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		return snd_sb8_compute_rate(pcm1, SND_PCM1_RECORD);
	}
	return -ENXIO;
}

static void snd_sb8_playback_prepare(snd_pcm1_t * pcm1,
				     unsigned char *buffer,
				     unsigned int size,
				     unsigned int offset,
				     unsigned int count)
{
	sbdsp_t *codec;
	snd_pcm1_channel_t *pchn;
	sbmixer_t *mixer;
	
	pchn = &pcm1->playback;
	codec = (sbdsp_t *) pcm1->private_data;
	if (pchn->mode & SND_PCM1_MODE_16) {
		size >>= 1;
 		count >>=1;
	}
	switch (codec->hardware) {
	case SB_HW_10:
		snd_mutex_lock(codec, reg);
		snd_sb8_ack_8bit(codec);
		snd_sbdsp_command(codec, SB_DSP_SAMPLE_RATE);
		snd_sbdsp_command(codec, codec->speed8);
		snd_sb8_ack_8bit(codec);
		snd_sbdsp_command(codec, SB_DSP_SPEAKER_ON);
		snd_mutex_unlock(codec, reg);
		snd_dma_program(codec->dma8, &buffer[offset], count, DMA_MODE_WRITE);
		codec->count8 = count - 1;
		break;
	case SB_HW_20:
	case SB_HW_201:
	case SB_HW_PRO:
		snd_mutex_lock(codec, reg);
		snd_sbdsp_command(codec, SB_DSP_SPEAKER_OFF);
		if (snd_sbdsp_reset(codec) < 0) {
			snd_mutex_unlock(codec, reg);
			snd_printk("sb8 - reset failed!!!\n");
			return;
		}
		snd_sbdsp_command(codec, SB_DSP_SPEAKER_OFF);
		if (codec->hardware == SB_HW_PRO) {
			mixer = &codec->mixer;
			snd_mutex_lock(mixer, mixer);
			if (pchn->voices > 1) {
				snd_sbmixer_write(mixer, 0x0e, snd_sbmixer_read(mixer, 0x0e) | 0x02);
			} else {
				snd_sbmixer_write(mixer, 0x0e, snd_sbmixer_read(mixer, 0x0e) & ~0x02);
			}
			snd_mutex_unlock(mixer, mixer);
		}
		snd_sb8_ack_8bit(codec);
		snd_sbdsp_command(codec, SB_DSP_SAMPLE_RATE);
		snd_sbdsp_command(codec, codec->speed8);
		snd_sb8_ack_8bit(codec);
		snd_mutex_unlock(codec, reg);
		snd_dma_program(codec->dma8, buffer, size, DMA_MODE_WRITE | DMA_MODE_AUTOINIT);
		codec->count8 = count - 1;
		snd_mutex_lock(codec, reg);
		snd_sbdsp_command(codec, SB_DSP_SPEAKER_ON);
		if (codec->fmt8 == SB_DSP_LO_OUTPUT_AUTO) {
			snd_sbdsp_command(codec, SB_DSP_BLOCK_SIZE);
			snd_sbdsp_command(codec, codec->count8 & 0xff);
			snd_sbdsp_command(codec, codec->count8 >> 8);
			snd_sbdsp_command(codec, codec->fmt8);
			snd_sbdsp_command(codec, SB_DSP_DMA8_OFF);
		}
		snd_mutex_unlock(codec, reg);
		break;
	default:
		snd_printd("unknown hardware for sb8 playback prepare!!!\n");
	}
}

static void snd_sb8_playback_trigger(snd_pcm1_t * pcm1, int up)
{
	sbdsp_t *codec;

	codec = (sbdsp_t *) pcm1->private_data;
	snd_mutex_lock(codec, reg);
	switch (codec->hardware) {
	case SB_HW_10:
		if (up) {
			codec->mode8 = SB_MODE8_PLAYBACK;
			if (snd_sbdsp_command(codec, SB_DSP_OUTPUT)) {
				snd_sbdsp_command(codec, codec->count8 & 0xff);
				snd_sbdsp_command(codec, codec->count8 >> 8);
			} else {
				snd_printk("SB 1.0 - unable to start output\n");
			}
		} else {
			snd_sbdsp_reset(codec);
			codec->mode8 = SB_MODE8_HALT;
		}
		break;
	case SB_HW_20:
	case SB_HW_201:
	case SB_HW_PRO:
		if (codec->fmt8 == SB_DSP_HI_OUTPUT_AUTO) {
			if (up) {
				snd_sbdsp_command(codec, SB_DSP_BLOCK_SIZE);
				snd_sbdsp_command(codec, codec->count8 & 0xff);
				snd_sbdsp_command(codec, codec->count8 >> 8);
				snd_sbdsp_command(codec, codec->fmt8);
			} else {
				snd_sbdsp_reset(codec);
			}
		} else {
			snd_sbdsp_command(codec, up ? SB_DSP_DMA8_ON : SB_DSP_DMA8_OFF);
		}
		codec->mode8 = up ? SB_MODE8_PLAYBACK : SB_MODE8_HALT;
		break;
	}
	snd_mutex_unlock(codec, reg);
}

static void snd_sb8_record_prepare(snd_pcm1_t * pcm1,
				   unsigned char *buffer,
				   unsigned int size,
				   unsigned int offset,
				   unsigned int count)
{
	sbdsp_t *codec;
	snd_pcm1_channel_t *pchn;
	sbmixer_t *mixer;
	
	pchn = &pcm1->record;
	codec = (sbdsp_t *) pcm1->private_data;
	switch (codec->hardware) {
	case SB_HW_10:
		snd_mutex_lock(codec, reg);
		snd_sbdsp_command(codec, SB_DSP_SPEAKER_OFF);
		snd_sb8_ack_8bit(codec);
		snd_sbdsp_command(codec, SB_DSP_SAMPLE_RATE);
		snd_sbdsp_command(codec, codec->speed8);
		snd_sb8_ack_8bit(codec);
		snd_mutex_unlock(codec, reg);
		snd_dma_program(codec->dma8, &buffer[offset], count, DMA_MODE_READ);
		codec->count8 = count - 1;
		break;
	case SB_HW_20:
	case SB_HW_201:
	case SB_HW_PRO:
		snd_mutex_lock(codec, reg);
		snd_sbdsp_command(codec, SB_DSP_SPEAKER_OFF);
		if (snd_sbdsp_reset(codec) < 0) {
			snd_mutex_unlock(codec, reg);
			snd_printk("sb8 - reset failed!!!\n");
			return;
		}
		snd_sbdsp_command(codec, SB_DSP_SPEAKER_OFF);
		if (codec->hardware == SB_HW_PRO) {
			mixer = &codec->mixer;
			snd_mutex_lock(mixer, mixer);
			if (pchn->voices > 1) {
				snd_sbmixer_write(mixer, 0x0e, snd_sbmixer_read(mixer, 0x0e) | 0x02);
			} else {
				snd_sbmixer_write(mixer, 0x0e, snd_sbmixer_read(mixer, 0x0e) & ~0x02);
			}
			snd_mutex_unlock(mixer, mixer);
		}
		if (codec->hardware == SB_HW_PRO) {
			snd_sbdsp_command(codec, pcm1->record.voices > 1 ? SB_DSP_STEREO_8BIT : SB_DSP_MONO_8BIT);
		}
		snd_sb8_ack_8bit(codec);
		snd_sbdsp_command(codec, SB_DSP_SAMPLE_RATE);
		snd_sbdsp_command(codec, codec->speed8);
		snd_sb8_ack_8bit(codec);
		snd_mutex_unlock(codec, reg);
		snd_dma_program(codec->dma8, buffer, size, DMA_MODE_READ | DMA_MODE_AUTOINIT);
		codec->count8 = count - 1;
		if (codec->fmt8 == SB_DSP_LO_INPUT_AUTO) {
			snd_mutex_lock(codec, reg);
			snd_sbdsp_command(codec, SB_DSP_BLOCK_SIZE);
			snd_sbdsp_command(codec, codec->count8 & 0xff);
			snd_sbdsp_command(codec, codec->count8 >> 8);
			snd_sbdsp_command(codec, codec->fmt8);
			snd_sbdsp_command(codec, SB_DSP_DMA8_OFF);
			snd_mutex_unlock(codec, reg);
		}
		break;
	default:
		snd_printd("unknown hardware for sb8 record prepare!!!\n");
	}
}

static void snd_sb8_record_trigger(snd_pcm1_t * pcm1, int up)
{
	sbdsp_t *codec;

	codec = (sbdsp_t *) pcm1->private_data;
	snd_mutex_lock(codec, reg);
	switch (codec->hardware) {
	case SB_HW_10:
		if (up) {
			codec->mode8 = SB_MODE8_RECORD;
			if (snd_sbdsp_command(codec, SB_DSP_INPUT)) {
				snd_sbdsp_command(codec, codec->count8 & 0xff);
				snd_sbdsp_command(codec, codec->count8 >> 8);
			} else {
				snd_printk("SB 1.0 - unable to start input\n");
			}
		} else {
			snd_sbdsp_reset(codec);
			codec->mode8 = SB_MODE8_HALT;
		}
		break;
	case SB_HW_20:
	case SB_HW_201:
	case SB_HW_PRO:
		if (codec->fmt8 == SB_DSP_HI_INPUT_AUTO) {
			if (up) {
				snd_sbdsp_command(codec, SB_DSP_BLOCK_SIZE);
				snd_sbdsp_command(codec, codec->count8 & 0xff);
				snd_sbdsp_command(codec, codec->count8 >> 8);
				snd_sbdsp_command(codec, codec->fmt8);
			} else {
				snd_sbdsp_reset(codec);
			}
		} else {
			snd_sbdsp_command(codec, up ? SB_DSP_DMA8_ON : SB_DSP_DMA8_OFF);
		}
		codec->mode8 = up ? SB_MODE8_RECORD : SB_MODE8_HALT;
		break;
	}
	snd_mutex_unlock(codec, reg);
}

void snd_sbdsp_sb8_interrupt(snd_pcm_t * pcm)
{
	snd_pcm1_t *pcm1;
	sbdsp_t *codec;

	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (sbdsp_t *) pcm1->private_data;
	switch (codec->mode8) {
	case SB_MODE8_PLAYBACK:	/* ok.. playback is active */
		pcm1->playback.ack(pcm1);
		break;
	case SB_MODE8_RECORD:
		pcm1->record.ack(pcm1);
		break;
	}
	snd_mutex_lock(codec, reg);
	snd_sb8_ack_8bit(codec);
	snd_mutex_unlock(codec, reg);
}

/*

 */

static int snd_sb8_playback_open(snd_pcm1_t * pcm1)
{
	sbdsp_t *codec = (sbdsp_t *) pcm1->private_data;
	int err;

	if ( atomic_set_value(&codec->open8, SB_OPEN_PCM) & SB_OPEN_PCM)
		return -EBUSY;
	if ((err = snd_pcm1_dma_alloc(pcm1, SND_PCM1_PLAYBACK, codec->dma8ptr, "Sound Blaster DSP")) < 0) {
		atomic_clr(&codec->open8, SB_OPEN_PCM);
		return err;
	}
	return 0;
}

static int snd_sb8_record_open(snd_pcm1_t * pcm1)
{
	sbdsp_t *codec = (sbdsp_t *) pcm1->private_data;
	int err;

	if ( atomic_set_value(&codec->open8, SB_OPEN_PCM) & SB_OPEN_PCM)
		return -EBUSY;
	if ((err = snd_pcm1_dma_alloc(pcm1, SND_PCM1_RECORD, codec->dma8ptr, "Sound Blaster DSP")) < 0) {
		atomic_clr(&codec->open8, SB_OPEN_PCM);
		return err;
	}
	return 0;
}

static void snd_sb8_playback_close(snd_pcm1_t * pcm1)
{
	sbdsp_t *codec = (sbdsp_t *) pcm1->private_data;

	snd_pcm1_dma_free(pcm1, SND_PCM1_PLAYBACK, codec->dma8ptr);
	atomic_clr(&codec->open8, SB_OPEN_PCM);
}

static void snd_sb8_record_close(snd_pcm1_t * pcm1)
{
	sbdsp_t *codec = (sbdsp_t *) pcm1->private_data;

	snd_pcm1_dma_free(pcm1, SND_PCM1_RECORD, codec->dma8ptr);
	atomic_clr(&codec->open8, SB_OPEN_PCM);
}

static unsigned int snd_sb8_playback_pointer(snd_pcm1_t * pcm1, unsigned int used_size)
{
	sbdsp_t *codec;

	codec = (sbdsp_t *) pcm1->private_data;
	if (codec->mode8 != SB_MODE8_PLAYBACK)
		return 0;
	return used_size - snd_dma_residue(codec->dma8);
}

static unsigned int snd_sb8_record_pointer(snd_pcm1_t * pcm1, unsigned int used_size)
{
	sbdsp_t *codec;

	codec = (sbdsp_t *) pcm1->private_data;
	if (codec->mode8 != SB_MODE8_RECORD)
		return 0;
	return used_size - snd_dma_residue(codec->dma8);
}

static void snd_sb8_record_8to16(snd_pcm1_t *pcm1, unsigned char *buffer,
				  unsigned int offset,
				  unsigned int count)
{
	register unsigned char *pbuffer;
	register unsigned int size1;
	unsigned char sbuffer[512];
	unsigned int size;

#if 0
	snd_printd("r8to16: buffer = 0x%lx, offset = %i, count = %i\n", (long) buffer, offset, count);
#endif
	buffer += offset >> 1;
	while (count > 0) {
		size = size1 = sizeof(sbuffer) < count ? sizeof(sbuffer) : count;
		pbuffer = sbuffer;	/* little endian */
		size1 >>= 1;
		while (size1-- > 0) {
			if (pcm1->record.mode & SND_PCM1_MODE_U)
				*pbuffer++ = 0x80;
			else
				*pbuffer++ = 0;
			*pbuffer++ = *buffer++;
		}
		snd_copy_to_user(pcm1->record.ctp, sbuffer, size);
		count -= size;
	}
}

static void snd_sb8_playback_16to8(snd_pcm1_t *pcm1, unsigned char *buffer,
				    unsigned int offset,
				    unsigned int count)
{
	register unsigned char *pbuffer;
	register unsigned int size1;
	unsigned char sbuffer[512];
	unsigned int size;

#if 0
	snd_printd("p16to8: buffer = 0x%lx, offset = %i, count = %i\n", (long) buffer, offset, count);
#endif
	buffer += offset >> 1;
	while (count > 0) {
		size = size1 = sizeof(sbuffer) < count ? sizeof(sbuffer) : count;
		snd_copy_from_user(pcm1->playback.ctp, sbuffer, size);
		pbuffer = sbuffer + 1;	/* little endian data */
		size1 >>= 1;
		while (size1-- > 0) {
			if (pcm1->playback.mode & SND_PCM1_MODE_U)
				*buffer++ = *pbuffer;
			else				
				*buffer++ = *pbuffer + 0x80;
			pbuffer += 2;
		}
		count -= size;
	}
}


static void snd_sb8_playback_dma(snd_pcm1_t * pcm1,
			      	  unsigned char *buffer, unsigned int offset,
				     unsigned int count)
{
	snd_pcm1_channel_t *pchn;

	pchn = &pcm1->playback;
	if (pchn->mode & SND_PCM1_MODE_16) {
		snd_sb8_playback_16to8(pcm1, buffer, offset, count);
		return;
	}
	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);
}

static void snd_sb8_record_dma(snd_pcm1_t * pcm1,
			        unsigned char *buffer, unsigned int offset,
				    unsigned int count)
{
	snd_pcm1_channel_t *pchn;

	pchn = &pcm1->record;
	if (pchn->mode & SND_PCM1_MODE_16) {
		snd_sb8_record_8to16(pcm1, buffer, offset, count);
		return;
	}
	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);
}

static void snd_sb8_playback_dma_move(snd_pcm1_t * pcm1,
				       unsigned char *dbuffer,
				       unsigned int dest_offset,
			 	       unsigned char *sbuffer,
			 	       unsigned int src_offset,
				       unsigned int count)
{
	snd_pcm1_channel_t *pchn;

	pchn = &pcm1->playback;
	if (pchn->mode & SND_PCM1_MODE_16) {
		memcpy(&dbuffer[dest_offset >> 1], &sbuffer[src_offset >> 1], count >> 1);
		return;
	}
	snd_pcm1_dma_move(pcm1, dbuffer, dest_offset, sbuffer, src_offset, count);
}

static void snd_sb8_record_dma_move(snd_pcm1_t * pcm1,
				     unsigned char *dbuffer,
				     unsigned int dest_offset,
			 	     unsigned char *sbuffer,
			 	     unsigned int src_offset,
				     unsigned int count)
{
	snd_pcm1_channel_t *pchn;

	snd_printd("record dma move, dest_offset = %i, src_offset = %i, count = %i\n", dest_offset, src_offset, count);

	pchn = &pcm1->record;
	if (pchn->mode & SND_PCM1_MODE_16) {
		memcpy(&dbuffer[dest_offset >> 1], &sbuffer[src_offset >> 1], count >> 1);
		return;
	}
	snd_pcm1_dma_move(pcm1, dbuffer, dest_offset, sbuffer, src_offset, count);
}

static void snd_sb8_playback_dma_neutral(snd_pcm1_t * pcm1,
			      		  unsigned char *buffer,
			      		  unsigned int offset,
					  unsigned int count,
					  unsigned char neutral_byte)
{
	snd_pcm1_channel_t *pchn;

	pchn = &pcm1->playback;
	if (pchn->mode & SND_PCM1_MODE_16) {
		memset(&buffer[offset >> 1], neutral_byte, count >> 1);
		return;
	}
	snd_pcm1_playback_dma_neutral(pcm1, buffer, offset, count, neutral_byte);
}

/*

 */

struct snd_stru_pcm1_hardware snd_sb8_playback =
{
	NULL,			/* private data */
	NULL,			/* private_free */
	SND_PCM1_HW_AUTODMA | SND_PCM1_HW_8BITONLY,	/* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 |
    SND_PCM_FMT_S16_LE | SND_PCM_FMT_U16_LE, /* formats */
	SND_PCM_FMT_U8,		/* hardware formats */
	0,			/* align value */
	6,			/* minimal fragment */
	4000,			/* min. rate */
	23000,			/* max. rate */
	1,			/* max. voices */
	snd_sb8_playback_open,
	snd_sb8_playback_close,
	snd_sb8_playback_ioctl,
	snd_sb8_playback_prepare,
	snd_sb8_playback_trigger,
	snd_sb8_playback_pointer,
	snd_sb8_playback_dma,
	snd_sb8_playback_dma_move,
	snd_sb8_playback_dma_neutral,
//	snd_pcm1_playback_dma_ulaw,
//	snd_pcm1_dma_move,
//	snd_pcm1_playback_dma_neutral
};

struct snd_stru_pcm1_hardware snd_sb8_record =
{
	NULL,			/* private data */
	NULL,			/* private free */
	SND_PCM1_HW_AUTODMA | SND_PCM1_HW_8BITONLY,	/* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 |    
    SND_PCM_FMT_S16_LE | SND_PCM_FMT_U16_LE, /* formats */
	SND_PCM_FMT_U8,		/* hardware formats */
	0,			/* align value */
	6,			/* minimal fragment */
	4000,			/* min. rate */
	13000,			/* max. rate */
	1,			/* max. voices */
	snd_sb8_record_open,
	snd_sb8_record_close,
	snd_sb8_record_ioctl,
	snd_sb8_record_prepare,
	snd_sb8_record_trigger,
	snd_sb8_record_pointer,
	snd_sb8_record_dma,
	snd_sb8_record_dma_move,
//	snd_pcm1_record_dma_ulaw,
//	snd_pcm1_dma_move,
	NULL
};
