/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *  Routines for control of 16-bit SoundBlaster cards and clones
 *  Note: This is very ugly hardware which uses one 8-bit DMA channel and
 *        second 16-bit DMA channel. Unfortunately 8-bit DMA channel can't
 *        transfer 16-bit samples and 16-bit DMA channels can't transfer
 *        8-bit samples. This make full duplex more complicated than
 *        can be... People, don't buy these soundcards for full 16-bit
 *        duplex!!!
 *  Note: 16-bit wide is assigned to first direction which made request.
 *        With full duplex - playback is preferred with abstract layer.
 *
 *
 *   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/sb.h"
#include "../../include/sb16_csp.h"
#include "../../include/info.h"

static inline void snd_sb16_ack_8bit(sbdsp_t * codec)
{
	inb(SBP(codec, DATA_AVAIL));
}

static inline void snd_sb16_ack_16bit(sbdsp_t * codec)
{
	inb(SBP(codec, DATA_AVAIL_16));
}

static void snd_sb16_setup_rate(sbdsp_t * codec,
				unsigned short rate,
				int channel)
{
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	if (codec->mode16 & (channel == SND_PCM_CHANNEL_PLAYBACK ? SB_MODE16_PLAYBACK16 : SB_MODE16_CAPTURE16))
		snd_sb16_ack_16bit(codec);
	else
		snd_sb16_ack_8bit(codec);
	if (!(codec->mode16 & SB_MODE16_RATE_LOCK)) {
		snd_sb16dsp_command(codec, SB_DSP_SAMPLE_RATE_IN);
		snd_sb16dsp_command(codec, rate >> 8);
		snd_sb16dsp_command(codec, rate & 0xff);
		snd_sb16dsp_command(codec, SB_DSP_SAMPLE_RATE_OUT);
		snd_sb16dsp_command(codec, rate >> 8);
		snd_sb16dsp_command(codec, rate & 0xff);
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
}

static int snd_sb16_playback_prepare(void *private_data, snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	sbdsp_t *codec = snd_magic_cast(sbdsp_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	unsigned char format;
	unsigned int size, count;

#ifdef CONFIG_SND_SB16_CSP
	if (codec->hardware == SB_HW_16CSP) {
		snd_sb_csp_callback_t *csp_callbacks = (snd_sb_csp_callback_t *) codec->csp_callbacks;
		snd_sb_csp_t *csp = snd_magic_cast(snd_sb_csp_t, codec->csp_private_data, -ENXIO);
		if (csp->running & SND_SB_CSP_ST_LOADED) {
			/* manually loaded codec */
			if ((csp->mode & SND_SB_CSP_MODE_DSP_WRITE) &&
			    ((1 << runtime->format.format) == csp->acc_format)) {
				/* Supported runtime PCM format for playback */
				if (csp_callbacks->csp_use(csp) == 0) {
					/* If CSP was successfully acquired */
					goto __start_CSP;
				}
			} else if ((csp->mode & SND_SB_CSP_MODE_QSOUND) && (csp->q_enabled)) {
				/* QSound decoder is loaded and enabled */
				if ((1 << runtime->format.format) & (SND_PCM_FMT_S8 | SND_PCM_FMT_U8 |
								     SND_PCM_FMT_S16_LE | SND_PCM_FMT_U16_LE)) {
					/* Only for simple PCM formats */
					if (csp_callbacks->csp_use(csp) == 0) {
						/* If CSP was successfully acquired */
						goto __start_CSP;
					}
				}
			}
		} else if (csp_callbacks->csp_use(csp) == 0) {
			/* Acquire CSP and try to autoload hardware codec */
			if (csp_callbacks->csp_autoload(csp, runtime->format.format, SND_SB_CSP_MODE_DSP_WRITE)) {
				/* Unsupported format, release CSP */
				csp_callbacks->csp_unuse(csp);
			} else {
		      __start_CSP:
				/* Try to start CSP */
				if (csp_callbacks->csp_start(csp, (codec->mode16 & SB_MODE16_PLAYBACK16) ?
							     SND_SB_CSP_SAMPLE_16BIT : SND_SB_CSP_SAMPLE_8BIT,
							     (runtime->format.voices > 1) ?
							     SND_SB_CSP_STEREO : SND_SB_CSP_MONO)) {
					/* Failed, release CSP */
					csp_callbacks->csp_unuse(csp);
				} else {
					/* Success, CSP acquired and running */
					codec->csp_acquired = SND_SB_CSP_MODE_DSP_WRITE;
				}
			}
		}
	}
#endif
	if (snd_pcm_format_unsigned(runtime->format.format) > 0) {
		format = runtime->format.voices > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO;
	} else {
		format = runtime->format.voices > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO;
	}

	snd_sb16_setup_rate(codec, runtime->format.rate, SND_PCM_CHANNEL_PLAYBACK);
#if 0
	snd_printk("playback - buffer = 0x%lx, size = %i (%i), count = %i\n", (long) buffer, size, pchn->size, count);
#endif
	size = codec->p_dma_size = snd_pcm_lib_transfer_size(subchn);
	count = snd_pcm_lib_transfer_fragment(subchn);
	if (codec->mode16 & SB_MODE16_PLAYBACK16) {	/* use 16-bit DMA */
		snd_dma_program(codec->dma16, runtime->dma_area->buf, size, DMA_MODE_WRITE | DMA_AUTOINIT);
		count >>= 1;
		count--;
		spin_lock_irqsave(&codec->reg_lock, flags);
		snd_sb16dsp_command(codec, SB_DSP_SPEAKER_ON);
		snd_sb16dsp_command(codec, SB_DSP4_OUT16_AI);
		snd_sb16dsp_command(codec, format);
		snd_sb16dsp_command(codec, count & 0xff);
		snd_sb16dsp_command(codec, count >> 8);
		snd_sb16dsp_command(codec, SB_DSP_DMA16_OFF);
		spin_unlock_irqrestore(&codec->reg_lock, flags);
	} else {
		snd_dma_program(codec->dma8, runtime->dma_area->buf, size, DMA_MODE_WRITE | DMA_AUTOINIT);
		count--;
		spin_lock_irqsave(&codec->reg_lock, flags);
		snd_sb16dsp_command(codec, SB_DSP_SPEAKER_ON);
		snd_sb16dsp_command(codec, SB_DSP4_OUT8_AI);
		snd_sb16dsp_command(codec, format);
		snd_sb16dsp_command(codec, count & 0xff);
		snd_sb16dsp_command(codec, count >> 8);
		snd_sb16dsp_command(codec, SB_DSP_DMA8_OFF);
		spin_unlock_irqrestore(&codec->reg_lock, flags);
	}
	return 0;
}

static int snd_sb16_playback_trigger(void *private_data,
				     snd_pcm_subchn_t * subchn,
				     int cmd)
{
	unsigned long flags;
	sbdsp_t *codec = snd_magic_cast(sbdsp_t, private_data, -ENXIO);
	int result = 0;

	spin_lock_irqsave(&codec->reg_lock, flags);
	if (cmd == SND_PCM_TRIGGER_GO) {
		snd_sb16dsp_command(codec, SB_DSP_SPEAKER_ON);
		snd_sb16dsp_command(codec, codec->mode16 & SB_MODE16_PLAYBACK16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
		codec->mode16 |= SB_MODE16_RATE_LOCK_P;
	} else if (cmd == SND_PCM_TRIGGER_STOP) {
		codec->mode16 &= ~SB_MODE16_RATE_LOCK_P;
		snd_sb16dsp_command(codec, codec->mode16 & SB_MODE16_PLAYBACK16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF);
		snd_sb16dsp_command(codec, SB_DSP_SPEAKER_OFF);
		/* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */
		if (codec->mode16 & SB_MODE16_RATE_LOCK_R)
			snd_sb16dsp_command(codec, codec->mode16 & SB_MODE16_CAPTURE16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
	} else {
		result = -EINVAL;
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return result;
}

static int snd_sb16_capture_prepare(void *private_data,
				    snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	sbdsp_t *codec = snd_magic_cast(sbdsp_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	unsigned char format;
	unsigned int size, count;

#ifdef CONFIG_SND_SB16_CSP
	if (codec->hardware == SB_HW_16CSP) {
		snd_sb_csp_callback_t *csp_callbacks = (snd_sb_csp_callback_t *) codec->csp_callbacks;
		snd_sb_csp_t *csp = snd_magic_cast(snd_sb_csp_t, codec->csp_private_data, -ENXIO);
		if (csp->running & SND_SB_CSP_ST_LOADED) {
			/* manually loaded codec */
			if ((csp->mode & SND_SB_CSP_MODE_DSP_READ) &&
			    ((1 << runtime->format.format) == csp->acc_format)) {
				/* Supported runtime PCM format for capture */
				if (csp_callbacks->csp_use(csp) == 0) {
					/* If CSP was successfully acquired */
					goto __start_CSP;
				}
			}
		} else if (csp_callbacks->csp_use(csp) == 0) {
			/* Acquire CSP and try to autoload hardware codec */
			if (csp_callbacks->csp_autoload(csp, runtime->format.format, SND_SB_CSP_MODE_DSP_READ)) {
				/* Unsupported format, release CSP */
				csp_callbacks->csp_unuse(csp);
			} else {
		      __start_CSP:
				/* Try to start CSP */
				if (csp_callbacks->csp_start(csp, (codec->mode16 & SB_MODE16_CAPTURE16) ?
							     SND_SB_CSP_SAMPLE_16BIT : SND_SB_CSP_SAMPLE_8BIT,
							     (runtime->format.voices > 1) ?
							     SND_SB_CSP_STEREO : SND_SB_CSP_MONO)) {
					/* Failed, release CSP */
					csp_callbacks->csp_unuse(csp);
				} else {
					/* Success, CSP acquired and running */
					codec->csp_acquired = SND_SB_CSP_MODE_DSP_READ;
				}
			}
		}
	}
#endif
	if (snd_pcm_format_unsigned(runtime->format.format) > 0) {
		format = runtime->format.voices > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO;
	} else {
		format = runtime->format.voices > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO;
	}
	snd_sb16_setup_rate(codec, runtime->format.rate, SND_PCM_CHANNEL_CAPTURE);
#if 0
	snd_printk("capture - buffer = 0x%lx, size = %i (%i), count = %i\n", (long) buffer, size, pchn->size, count);
#endif
	size = codec->c_dma_size = snd_pcm_lib_transfer_size(subchn);
	count = snd_pcm_lib_transfer_fragment(subchn);
	if (codec->mode16 & SB_MODE16_CAPTURE16) {	/* use 16-bit DMA */
		snd_dma_program(codec->dma16, runtime->dma_area->buf, size, DMA_MODE_READ | DMA_AUTOINIT);
		count >>= 1;
		count--;
		spin_lock_irqsave(&codec->reg_lock, flags);
		snd_sb16dsp_command(codec, SB_DSP4_IN16_AI);
		snd_sb16dsp_command(codec, format);
		snd_sb16dsp_command(codec, count & 0xff);
		snd_sb16dsp_command(codec, count >> 8);
		snd_sb16dsp_command(codec, SB_DSP_DMA16_OFF);
		spin_unlock_irqrestore(&codec->reg_lock, flags);
	} else {
		snd_dma_program(codec->dma8, runtime->dma_area->buf, size, DMA_MODE_READ | DMA_AUTOINIT);
		count--;
		spin_lock_irqsave(&codec->reg_lock, flags);
		snd_sb16dsp_command(codec, SB_DSP4_IN8_AI);
		snd_sb16dsp_command(codec, format);
		snd_sb16dsp_command(codec, count & 0xff);
		snd_sb16dsp_command(codec, count >> 8);
		snd_sb16dsp_command(codec, SB_DSP_DMA8_OFF);
		spin_unlock_irqrestore(&codec->reg_lock, flags);
	}
	return 0;
}

static int snd_sb16_capture_trigger(void *private_data,
				    snd_pcm_subchn_t * subchn,
				    int cmd)
{
	unsigned long flags;
	sbdsp_t *codec = snd_magic_cast(sbdsp_t, private_data, -ENXIO);
	int result = 0;

	spin_lock_irqsave(&codec->reg_lock, flags);
	if (cmd == SND_PCM_TRIGGER_GO) {
		codec->mode16 |= SB_MODE16_RATE_LOCK_R;
		snd_sb16dsp_command(codec, codec->mode16 & SB_MODE16_CAPTURE16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
	} else if (cmd == SND_PCM_TRIGGER_STOP) {
		codec->mode16 &= ~SB_MODE16_RATE_LOCK_R;
		snd_sb16dsp_command(codec, codec->mode16 & SB_MODE16_CAPTURE16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF);
		/* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */
		if (codec->mode16 & SB_MODE16_RATE_LOCK_P)
			snd_sb16dsp_command(codec, codec->mode16 & SB_MODE16_PLAYBACK16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
	} else {
		result = -EINVAL;
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return result;
}

void snd_sb16dsp_interrupt(snd_pcm_t * pcm, unsigned short status)
{
	int ok;
	sbdsp_t *codec = snd_magic_cast(sbdsp_t, pcm->private_data, );

#if 0
	PRINTK("sb16: interrupt - status = 0x%x\n", status);
#endif
	if (status & SB_IRQTYPE_8BIT) {
		ok = 0;
		if ((codec->mode16 & (SB_MODE16_PLAYBACK16 | SB_MODE16_PLAYBACK)) == SB_MODE16_PLAYBACK) {
			snd_pcm_transfer_done(codec->playback_subchn);
			ok++;
		}
		if ((codec->mode16 & (SB_MODE16_CAPTURE16 | SB_MODE16_CAPTURE)) == SB_MODE16_CAPTURE) {
			snd_pcm_transfer_done(codec->capture_subchn);
			ok++;
		}
		spin_lock(&codec->reg_lock);
#ifdef CONFIG_SND_SB16_CSP
		if (codec->hardware == SB_HW_16CSP) {
			snd_sb_csp_t *csp = snd_magic_cast(snd_sb_csp_t, codec->csp_private_data, );
			if (csp->qpos_changed) {
				snd_sb_csp_callback_t *csp_callbacks = (snd_sb_csp_callback_t *) codec->csp_callbacks;
				csp_callbacks->csp_qsound_transfer (csp);
			}
		}
#endif
		if (!ok)
			snd_sb16dsp_command(codec, SB_DSP_DMA8_OFF);
		snd_sb16_ack_8bit(codec);
		spin_unlock(&codec->reg_lock);
	}
	if (status & SB_IRQTYPE_16BIT) {
		ok = 0;
		if ((codec->mode16 & (SB_MODE16_PLAYBACK16 | SB_MODE16_PLAYBACK)) == (SB_MODE16_PLAYBACK16 | SB_MODE16_PLAYBACK)) {
			snd_pcm_transfer_done(codec->playback_subchn);
			ok++;
		}
		if ((codec->mode16 & (SB_MODE16_CAPTURE16 | SB_MODE16_CAPTURE)) == (SB_MODE16_CAPTURE16 | SB_MODE16_CAPTURE)) {
			snd_pcm_transfer_done(codec->capture_subchn);
			ok++;
		}
		spin_lock(&codec->reg_lock);
#ifdef CONFIG_SND_SB16_CSP
		if (codec->hardware == SB_HW_16CSP) {
			snd_sb_csp_t *csp = snd_magic_cast(snd_sb_csp_t, codec->csp_private_data, );
			if (csp->qpos_changed) {
				snd_sb_csp_callback_t *csp_callbacks = (snd_sb_csp_callback_t *) codec->csp_callbacks;
				csp_callbacks->csp_qsound_transfer (csp);
			}
		}
#endif
		if (!ok)
			snd_sb16dsp_command(codec, SB_DSP_DMA16_OFF);
		snd_sb16_ack_16bit(codec);
		spin_unlock(&codec->reg_lock);
	}
}

/*

 */

static unsigned int snd_sb16_playback_pointer(void *private_data,
					      snd_pcm_subchn_t * subchn)
{
	sbdsp_t *codec = snd_magic_cast(sbdsp_t, private_data, -ENXIO);

	if (codec->mode16 & SB_MODE16_PLAYBACK16) {
		return codec->p_dma_size - snd_dma_residue(codec->dma16);
	} else {
		return codec->p_dma_size - snd_dma_residue(codec->dma8);
	}
}

static unsigned int snd_sb16_capture_pointer(void *private_data,
					     snd_pcm_subchn_t * subchn)
{
	sbdsp_t *codec = snd_magic_cast(sbdsp_t, private_data, -ENXIO);

	if (codec->mode16 & SB_MODE16_CAPTURE16) {
		return codec->c_dma_size - snd_dma_residue(codec->dma16);
	} else {
		return codec->c_dma_size - snd_dma_residue(codec->dma8);
	}
}

/*

 */

static snd_pcm_hardware_t snd_sb16_playback =
{
	SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_STREAM |
	SND_PCM_CHNINFO_BLOCK | SND_PCM_CHNINFO_INTERLEAVE |
	SND_PCM_CHNINFO_MMAP_VALID,	/* flags */
	0,			/* formats */
	SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_44100,
	4000,			/* min. rate */
	44100,			/* max. rate */
	1,			/* min. voices */
	2,			/* max. voices */
	64,			/* min. fragment size */
	65536,			/* max. fragment size */
	3,			/* fragment align */
	0,			/* FIFO size (uknown) */
	2,			/* transfer block size */
	snd_pcm_lib_ioctl,
	snd_sb16_playback_prepare,
	snd_sb16_playback_trigger,
	snd_sb16_playback_pointer
};

static snd_pcm_hardware_t snd_sb16_capture =
{
	SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_STREAM |
	SND_PCM_CHNINFO_BLOCK | SND_PCM_CHNINFO_INTERLEAVE |
	SND_PCM_CHNINFO_MMAP_VALID,	/* flags */
	0,			/* formats */
	SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_44100,
	4000,			/* min. rate */
	44100,			/* max. rate */
	1,			/* min. voices */
	2,			/* max. voices */
	64,			/* min. fragment size */
	65536,			/* max. fragment size */
	3,			/* fragment align */
	0,			/* FIFO size (uknown) */
	2,			/* transfer block size */
	snd_pcm_lib_ioctl,
	snd_sb16_capture_prepare,
	snd_sb16_capture_trigger,
	snd_sb16_capture_pointer
};

/*
 *  open/close
 */

int snd_sb16_playback_open(void *private_data, snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	sbdsp_t *codec = snd_magic_cast(sbdsp_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int err = -EAGAIN;

	runtime->hw = (snd_pcm_hardware_t *) snd_kmalloc(sizeof(*runtime->hw), GFP_KERNEL);
	if (runtime->hw == NULL)
		return -ENOMEM;
	memcpy(runtime->hw, &snd_sb16_playback, sizeof(*runtime->hw));
	runtime->hw_free = _snd_kfree;
	snd_pcm_set_mixer(subchn, codec->kmixer->device, codec->mixer.me_playback);
	runtime->private_data = codec;
	if (codec->dma16ptr &&
	 (codec->force_mode16 & (SB_MODE16_PLAYBACK | SB_MODE16_AUTO))) {
		if ((err = snd_pcm_dma_alloc(subchn, codec->dma16ptr, "Sound Blaster 16 DSP")) >= 0) {
			spin_lock_irqsave(&codec->open16_lock, flags);
			codec->mode16 |= SB_MODE16_PLAYBACK16 | SB_MODE16_PLAYBACK;
			runtime->hw->formats = SND_PCM_FMT_S16_LE | SND_PCM_FMT_U16_LE;
#ifdef CONFIG_SND_SB16_CSP
			/* CSP decoders (QSound excluded) support only 16bit transfers */
			if (codec->hardware == SB_HW_16CSP) {
				snd_sb_csp_t *csp = snd_magic_cast(snd_sb_csp_t, codec->csp_private_data, -ENXIO);
				if (csp->running & SND_SB_CSP_ST_LOADED) {
					/* manually loaded codec */
					if (csp->mode & SND_SB_CSP_MODE_DSP_WRITE) {
						runtime->hw->formats |= csp->acc_format;
					}
				} else {
					/* autoloaded codecs */
					runtime->hw->formats |= SND_PCM_FMT_MU_LAW | SND_PCM_FMT_A_LAW |
								SND_PCM_FMT_IMA_ADPCM;
				}
			}
#endif
			goto __open_ok;
		}
	}
	if (codec->dma8ptr &&
	    ((codec->force_mode16 & (SB_MODE16_PLAYBACK | SB_MODE16_AUTO)) == SB_MODE16_AUTO ||
	     (codec->force_mode16 & (SB_MODE16_PLAYBACK | SB_MODE16_AUTO)) == 0)) {
		if ((err = snd_pcm_dma_alloc(subchn, codec->dma8ptr, "Sound Blaster 16 DSP")) >= 0) {
			spin_lock_irqsave(&codec->open16_lock, flags);
			codec->mode16 &= ~SB_MODE16_PLAYBACK16;
			codec->mode16 |= SB_MODE16_PLAYBACK;
			runtime->hw->formats = SND_PCM_FMT_U8 | SND_PCM_FMT_S8;
			goto __open_ok;
		}
	}
	return err;
      __open_ok:
	if (codec->hardware == SB_HW_ALS100)
		runtime->hw->max_rate = 48000;
	codec->playback_subchn = subchn;
	spin_unlock_irqrestore(&codec->open16_lock, flags);
	return 0;
}

int snd_sb16_capture_open(void *private_data, snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	sbdsp_t *codec = snd_magic_cast(sbdsp_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int err = -EAGAIN;

	runtime->hw = (snd_pcm_hardware_t *) snd_kmalloc(sizeof(*runtime->hw), GFP_KERNEL);
	if (runtime->hw == NULL)
		return -ENOMEM;
	memcpy(runtime->hw, &snd_sb16_capture, sizeof(*runtime->hw));
	runtime->hw_free = _snd_kfree;
	snd_pcm_set_mixer(subchn, codec->kmixer->device, codec->mixer.me_playback);
	runtime->private_data = codec;
	if (codec->dma16ptr &&
	    (codec->force_mode16 & (SB_MODE16_CAPTURE | SB_MODE16_AUTO))) {
		if ((err = snd_pcm_dma_alloc(subchn, codec->dma16ptr, "Sound Blaster 16 DSP")) >= 0) {
			spin_lock_irqsave(&codec->open16_lock, flags);
			codec->mode16 |= SB_MODE16_CAPTURE16 | SB_MODE16_CAPTURE;
			runtime->hw->formats = SND_PCM_FMT_S16_LE | SND_PCM_FMT_U16_LE;
#ifdef CONFIG_SND_SB16_CSP
			/* CSP coders support only 16bit transfers */
			if (codec->hardware == SB_HW_16CSP) {
				snd_sb_csp_t *csp = snd_magic_cast(snd_sb_csp_t, codec->csp_private_data, -ENXIO);
				if (csp->running & SND_SB_CSP_ST_LOADED) {
					/* manually loaded codec */
					if (csp->mode & SND_SB_CSP_MODE_DSP_READ) {
						runtime->hw->formats |= csp->acc_format;
					}
				} else {
					/* autoloaded codecs */
					runtime->hw->formats |= SND_PCM_FMT_MU_LAW | SND_PCM_FMT_A_LAW |
								SND_PCM_FMT_IMA_ADPCM;
				}
			}
#endif
			goto __open_ok;
		}
	}
	if (codec->dma8ptr &&
	    ((codec->force_mode16 & (SB_MODE16_CAPTURE | SB_MODE16_AUTO)) == SB_MODE16_AUTO ||
	     (codec->force_mode16 & (SB_MODE16_CAPTURE | SB_MODE16_AUTO)) == 0)) {
		if ((err = snd_pcm_dma_alloc(subchn, codec->dma8ptr, "Sound Blaster 16 DSP")) >= 0) {
			spin_lock_irqsave(&codec->open16_lock, flags);
			codec->mode16 &= ~SB_MODE16_CAPTURE16;
			codec->mode16 |= SB_MODE16_CAPTURE;
			runtime->hw->formats = SND_PCM_FMT_U8 | SND_PCM_FMT_S8;
			goto __open_ok;
		}
	}
	return err;
      __open_ok:
	if (codec->hardware == SB_HW_ALS100)
		runtime->hw->max_rate = 48000;
	codec->capture_subchn = subchn;
	spin_unlock_irqrestore(&codec->open16_lock, flags);
	return 0;
}

int snd_sb16_playback_close(void *private_data, snd_pcm_subchn_t * subchn)
{
	sbdsp_t *codec = snd_magic_cast(sbdsp_t, private_data, -ENXIO);

#ifdef CONFIG_SND_SB16_CSP
	if ((codec->hardware == SB_HW_16CSP) & (codec->csp_acquired == SND_SB_CSP_MODE_DSP_WRITE)) {
		snd_sb_csp_callback_t *csp_callbacks = (snd_sb_csp_callback_t *) codec->csp_callbacks;
		snd_sb_csp_t *csp = snd_magic_cast(snd_sb_csp_t, codec->csp_private_data, -ENXIO);

		if (csp_callbacks->csp_stop(csp) == 0) {
			csp_callbacks->csp_unuse(csp);
			codec->csp_acquired = 0;
		}
	}
#endif
	codec->playback_subchn = NULL;
	snd_pcm_dma_free(subchn);
	codec->mode16 &= ~(SB_MODE16_PLAYBACK | SB_MODE16_PLAYBACK16);
	return 0;
}

int snd_sb16_capture_close(void *private_data, snd_pcm_subchn_t * subchn)
{
	sbdsp_t *codec = snd_magic_cast(sbdsp_t, private_data, -ENXIO);

#ifdef CONFIG_SND_SB16_CSP
	if ((codec->hardware == SB_HW_16CSP) & (codec->csp_acquired == SND_SB_CSP_MODE_DSP_READ)) {
		snd_sb_csp_callback_t *csp_callbacks = codec->csp_callbacks;
		snd_sb_csp_t *csp = snd_magic_cast(snd_sb_csp_t, codec->csp_private_data, -ENXIO);

		if (csp_callbacks->csp_stop(csp) == 0) {
			csp_callbacks->csp_unuse(csp);
			codec->csp_acquired = 0;
		}
	}
#endif
	codec->capture_subchn = NULL;
	snd_pcm_dma_free(subchn);
	codec->mode16 &= ~(SB_MODE16_CAPTURE | SB_MODE16_CAPTURE16);
	return 0;
}

/*
 *  switch interface
 */

static int snd_sb16_set_dma_mode(sbdsp_t * codec, int what)
{
	if (codec->dma8ptr == NULL || codec->dma16ptr == NULL) {
		snd_debug_check(what != 0, -EINVAL);
		return 0;
	}
	if (what == 0) {
		codec->force_mode16 = SB_MODE16_AUTO;
	} else if (what == 1) {
		codec->force_mode16 = SB_MODE16_PLAYBACK;
	} else if (what == 2) {
		codec->force_mode16 = SB_MODE16_CAPTURE;
	} else {
		return -EINVAL;
	}
	return 0;
}

static int snd_sb16_get_dma_mode(sbdsp_t * codec)
{
	if (codec->dma8ptr == NULL || codec->dma16ptr == NULL)
		return 0;
	switch (codec->force_mode16) {
	case SB_MODE16_PLAYBACK:
		return 1;
	case SB_MODE16_CAPTURE:
		return 2;
	default:
		return 0;
	}
}

static int snd_sb16_set_dma_switch(void *private_data, snd_kswitch_t * kswitch, snd_switch_t * uswitch)
{
	int change;
	sbdsp_t *codec = snd_magic_cast(sbdsp_t, kswitch->private_data, -ENXIO);

	if (uswitch->type != SND_SW_TYPE_BYTE ||
	    uswitch->value.data8[0] > 2)
		return -EINVAL;
	change = snd_sb16_get_dma_mode(codec) != uswitch->value.data8[0];
	snd_sb16_set_dma_mode(codec, uswitch->value.data8[0]);
	return change;
}

static int snd_sb16_get_dma_switch(void *private_data, snd_kswitch_t * kswitch, snd_switch_t * uswitch)
{
	sbdsp_t *codec = snd_magic_cast(sbdsp_t, kswitch->private_data, -ENXIO);

	uswitch->type = SND_SW_TYPE_BYTE;
	uswitch->low = 0;
	uswitch->high = 2;
	uswitch->value.data8[0] = snd_sb16_get_dma_mode(codec);
	return 0;
}

snd_kswitch_t snd_sb16_dma_switch =
{
	name:	"DMA 16-bit (0=auto,1=p,2=c)",
	get:	snd_sb16_get_dma_switch,
	set:	snd_sb16_set_dma_switch,
};
