/*
 * Driver for C-Media CMI8338 and 8738 PCI soundcards.
 * Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de>
 *
 *   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.
 */
 
#define __SND_OSS_COMPAT__
#define SND_MAIN_OBJECT_FILE

#include "../include/driver.h"
#include "../include/info.h"
#include "../include/control.h"
#include "../include/mixer.h"
#include "../include/pcm.h"
#include "../include/rawmidi.h"
#include "../include/mpu401.h"
#include "../include/sb.h"	/* for SB16 mixer */
#include "../include/initval.h"

MODULE_DESCRIPTION("\
Driver: C-Media CMI8x38 PCI\n\
Card: C-Media CMI8338 PCI\n\
Card: C-Media CMI8738 PCI\n\
PCI: 0x13f6=0x0100\n\
PCI: 0x13f6=0x0101\n\
PCI: 0x13f6=0x0111\n\
");

int snd_index[SND_CARDS] = SND_DEFAULT_IDX;	/* Index 0-MAX */
char *snd_id[SND_CARDS] = SND_DEFAULT_STR;	/* ID for this card */
int snd_enable[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 1}; /* all enabled */
int snd_dac_frame_size[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 128};
int snd_adc_frame_size[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 128};
int snd_enable_midi[SND_CARDS] = {[0 ... (SND_CARDS-1)] = 0};
int snd_enable_fm[SND_CARDS] = {[0 ... (SND_CARDS-1)] = 0};
int snd_mpu_port[SND_CARDS] = {0x330, [1 ... (SND_CARDS-1)]=-1};
int snd_fm_port[SND_CARDS] = {0x388, [1 ... (SND_CARDS-1)]=-1};
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for C-Media PCI soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for C-Media PCI soundcard.");
MODULE_PARM(snd_enable, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_enable, "Enable this soundcard. [BOOL]");
MODULE_PARM(snd_dac_frame_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dac_frame_size, "DAC frame size in kB for C-Media PCI soundcard.");
MODULE_PARM(snd_adc_frame_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_adc_frame_size, "ADC frame size in kB for C-Media PCI soundcard.");
MODULE_PARM(snd_enable_midi, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_enable_midi, "Enable MPU-401 port. [BOOL]");
MODULE_PARM(snd_mpu_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_mpu_port, "MPU-401 port. [list=0x330,0x320,0x310,0x300]");
MODULE_PARM(snd_enable_fm, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_enable_fm, "Enable FM OPL-3 synth. [BOOL]");
MODULE_PARM(snd_fm_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_fm_port, "FM port. [list=0x388,0x3c8,0x3e0,0x3e8]");

/*
 * CM8338 registers definition
 */

#define CM_REG_FUNCTRL0		0x00
#define CM_RST_CH1		0x00080000
#define CM_RST_CH0		0x00040000
#define CM_CHEN1		0x00020000
#define CM_CHEN0		0x00010000
#define CM_PAUSE1		0x00000008
#define CM_PAUSE0		0x00000004
#define CM_CHADC1		0x00000002	/* ch1, 0:playback, 1:record */
#define CM_CHADC0		0x00000001	/* ch0, 0:playback, 1:record */

#define CM_REG_FUNCTRL1		0x04
#define CM_ASFC_MASK		0x0000E000	/* ADC sampling frequency */
#define CM_ASFC_SHIFT		13
#define CM_DSFC_MASK		0x00001C00	/* DAC sampling frequency */
#define CM_DSFC_SHIFT		10
#define CM_SPDF_1		0x00000200	/* SPDIF IN/OUT at channel B */
#define CM_SPDF_0		0x00000100	/* SPDIF OUT only channel A */
#define CM_SPDFLOOP		0x00000080	/* ext. SPDIIF/OUT -> IN loopback */
#define CM_SPDO2DAC		0x00000040	/* SPDIF/OUT can be heard from internal DAC */
#define CM_INTRM		0x00000020	/* master control block (MCB) interrupt enabled */
#define CM_BREQ			0x00000010	/* bus master enabled */
#define CM_VOICE_EN		0x00000008	/* legacy voice (SB16,FM) */
#define CM_UART_EN		0x00000004	/* UART */
#define CM_JYSTK_EN		0x00000002	/* joy stick */

#define CM_REG_CHFORMAT		0x08

#define CM_ADCBITLEN_MASK	0x0000C000	
#define CM_ADCBITLEN_16		0x00000000
#define CM_ADCBITLEN_15		0x00004000
#define CM_ADCBITLEN_14		0x00008000
#define CM_ADCBITLEN_13		0x0000C000

#define CM_ADCDACLEN_MASK	0x00003000
#define CM_ADCDACLEN_060	0x00000000
#define CM_ADCDACLEN_066	0x00001000
#define CM_ADCDACLEN_130	0x00002000
#define CM_ADCDACLEN_280	0x00003000

#define CM_CH1FMT_MASK		0x0000000C
#define CM_CH1FMT_SHIFT		2
#define CM_CH0FMT_MASK		0x00000003
#define CM_CH0FMT_SHIFT		0

#define CM_REG_INT_HLDCLR	0x0C
#define CM_TDMA_INT_EN		0x00040000
#define CM_CH1_INT_EN		0x00020000
#define CM_CH0_INT_EN		0x00010000
#define CM_INT_HOLD		0x00000002
#define CM_INT_CLEAR		0x00000001

#define CM_REG_INT_STATUS	0x10
#define CM_INTR			0x80000000
#define CM_UARTINT		0x00010000
#define CM_LTDMAINT		0x00008000
#define CM_HTDMAINT		0x00004000
#define CM_CH1BUSY		0x00000008
#define CM_CH0BUSY		0x00000004
#define CM_CHINT1		0x00000002
#define CM_CHINT0		0x00000001

#define CM_REG_LEGACY_CTRL	0x14
#define CM_VMPU_MASK		0x60000000	/* MPU401 i/o port address */
#define CM_VMPU_330		0x00000000
#define CM_VMPU_320		0x20000000
#define CM_VMPU_310		0x40000000
#define CM_VMPU_300		0x60000000
#define CM_VSBSEL_MASK		0x0C000000	/* SB16 base address */
#define CM_VSBSEL_220		0x00000000
#define CM_VSBSEL_240		0x04000000
#define CM_VSBSEL_260		0x08000000
#define CM_VSBSEL_280		0x0C000000
#define CM_FMSEL_MASK		0x03000000	/* FM OPL3 base address */
#define CM_FMSEL_388		0x00000000
#define CM_FMSEL_3C8		0x01000000
#define CM_FMSEL_3E0		0x02000000
#define CM_FMSEL_3E8		0x03000000
#define CM_ENSPDOUT		0x00800000	/* enable XPDIF/OUT to I/O interface */
#define CM_SPDCOPYRHT		0x00400000
#define CM_DAC2SPDO		0x00200000	/* enable wave+fm_midi -> SPDIF/OUT */
#define CM_SETRETRY		0x00010000	/* 0: legacy i/o wait (default), 1: legacy i/o bus retry */

#define CM_REG_MISC_CTRL	0x18
#define CM_PWD			0x80000000
#define CM_RESET		0x40000000
#define CM_SFIL_MASK		0x30000000
#define CM_TXVX			0x08000000
#define CM_N4SPK3D		0x04000000
#define CM_SPDO5V		0x02000000
#define CM_SPDIF48K		0x01000000	/* write */
#define CM_SPATUS48K		0x01000000	/* read */
#define CM_ENDBDAC		0x00800000
#define CM_XCHGDAC		0x00400000	/* 0: front=ch0, 1: front=ch1 */
#define CM_SPD32SEL		0x00200000	/* 0: 16bit SPDIF, 1: 32bit */
#define CM_SPDFLOOPI		0x00100000	/* int. SPDIF-IN -> int. OUT */
#define CM_FM_EN		0x00080000	/* enalbe FM */
#define CM_VIDWPDSB		0x00010000 
#define CM_SPDF_AC97		0x00008000	/* 0: SPDIF/OUT 44.1K, 1: 48K */
#define CM_MASK_EN		0x00004000
#define CM_VIDWPPRT		0x00002000
#define CM_SFILENB		0x00001000
#define CM_MMODE_MASK		0x00000E00
#define CM_FLINKON		0x00000080
#define CM_FLINKOFF		0x00000040
#define CM_MIDSMP		0x00000010
#define CM_UPDDMA_MASK		0x0000000C
#define CM_TWAIT_MASK		0x00000003

	/* byte */
#define CM_REG_MIXER0	0x20

#define CM_REG_SB16_DATA	0x22
#define CM_REG_SB16_ADDR	0x23

#define CM_REG_MIXER1		0x24
#define CM_FMMUTE		0x80	/* mute FM */
#define CM_WSMUTE		0x40	/* mute PCM */
#define CM_SPK4			0x20	/* lin-in -> line out */
#define CM_REAR2FRONT		0x10	/* exchange rear/front */
#define CM_WAVEINL		0x08	/* digital wave rec. left chan */
#define CM_WAVEINR		0x04
#define CM_X3DEN		0x02	/* 3D surround enable */
#define CM_CDPLAY		0x01	/* enable SPDIF/IN PCM -> DAC */

#define CM_REG_MIXER2		0x25
#define CM_RAUXREN		0x80
#define CM_RAUXLEN		0x40
#define CM_VAUXRM		0x20
#define CM_VAUXLM		0x10
#define CM_VADMIC_MASK		0x0e
#define CM_MICGAINZ		0x01

#define CM_REG_AUX_VOL		0x26
#define CM_VAUXL_MASK		0xf0
#define CM_VAUXR_MASK		0x0f

#define CM_REG_MISC		0x27
#define CM_REG_AC97		0x28

#define CM_REG_CH0_FRAME1	0x80
#define CM_REG_CH0_FRAME2	0x84
#define CM_REG_CH1_FRAME1	0x88
#define CM_REG_CH1_FRAME2	0x8C

#define CM_EXTENT_CODEC	  0x100
#define CM_EXTENT_MIDI	  0x2
#define CM_EXTENT_SYNTH	  0x4

/*
 */

typedef struct snd_card_cmipci snd_card_cmipci_t;
typedef struct snd_stru_cmipci cmipci_t;

struct snd_card_cmipci {
	struct pci_dev *pci;
	snd_irq_t *irqptr;

	unsigned long iobase, iomidi, iosynth;
	int type;

	/* switches */
	int enable_midi;
	int enable_synth;

	snd_dma_t *dma1ptr;	/* DAC */
	snd_dma_t *dma2ptr;	/* ADC */
	snd_card_t *card;
	snd_pcm_t *pcm;		/* DAC/ADC PCM */
	snd_kmixer_t *mixer;
	snd_rawmidi_t *rmidi;
	cmipci_t *cmipci;
};
	

/*
 */

typedef struct snd_stru_cmipci_pcm cmipci_pcm_t;
struct snd_stru_cmipci_pcm {
	snd_pcm_subchn_t *subchn;
	int running;
	unsigned int dma_size;
	unsigned int frag_size;
	unsigned int offset;
	unsigned int fmt;
	int ch, dac;
};

struct snd_stru_cmipci {
	snd_card_t *card;

	struct pci_dev *pci;
	snd_irq_t * irqptr;

	unsigned long iobase, iomidi, iosynth;
	unsigned int ctrl;

	snd_dma_t *dma1ptr;	/* DAC */
	snd_dma_t *dma2ptr;	/* ADC */

	snd_pcm_t *pcm;		/* DAC/ADC PCM */

	cmipci_pcm_t playback, capture;

	/* mixers */
	snd_kmixer_t *kmixer;

	snd_kmixer_element_t *me_mux_mic;
	snd_kmixer_element_t *me_mux_line;
	snd_kmixer_element_t *me_mux_cd;
	snd_kmixer_element_t *me_mux;
	snd_kmixer_element_t *me_in_accu;
	snd_kmixer_element_t *me_out_accu;
	snd_kmixer_element_t *me_playback;
	snd_kmixer_element_t *me_capture;
	snd_kmixer_element_t *me_in_speaker;
	snd_kmixer_element_t *me_vol_speaker;
	snd_kmixer_element_t *me_in_mic;
	snd_kmixer_element_t *me_vol_mic;
	snd_kmixer_element_t *me_sw1_mic_output;
	snd_kmixer_element_t *me_sw1_mic_input;
	snd_kmixer_element_t *me_in_line;
	snd_kmixer_element_t *me_vol_line;
	snd_kmixer_element_t *me_sw1_line_output;
	snd_kmixer_element_t *me_sw3_line_input;
	snd_kmixer_element_t *me_in_cd;
	snd_kmixer_element_t *me_vol_cd;
	snd_kmixer_element_t *me_sw1_cd_output;
	snd_kmixer_element_t *me_sw3_cd_input;
	snd_kmixer_element_t *me_in_synth;
	snd_kmixer_element_t *me_vol_synth;
	snd_kmixer_element_t *me_sw2_synth;
	snd_kmixer_element_t *me_sw3_synth_input;
	snd_kmixer_element_t *me_vol_pcm;
	snd_kmixer_element_t *me_sw1_pcm_input;
	snd_kmixer_element_t *me_sw2_mute_pcm;
	snd_kmixer_element_t *me_out_master;
	snd_kmixer_element_t *me_sw1_3dse;
	snd_kmixer_element_t *me_vol_master;
	
	/* external MIDI */
	snd_rawmidi_t *rmidi;

	spinlock_t reg_lock;
	snd_info_entry_t *proc_entry;
};


/* read/write operations for dword register */
inline static void snd_cmipci_write(cmipci_t *cm, unsigned int cmd, unsigned int data)
{
	outl(data, cm->iobase + cmd);
}
inline static unsigned int snd_cmipci_read(cmipci_t *cm, unsigned int cmd)
{
	return inl(cm->iobase + cmd);
}

/* bit operations for dword register */
static void snd_cmipci_set_bit(cmipci_t *cm, unsigned int cmd, unsigned int flag)
{
	unsigned int val;
	val = inl(cm->iobase + cmd);
	val |= flag;
	outl(val, cm->iobase + cmd);
}

static void snd_cmipci_clear_bit(cmipci_t *cm, unsigned int cmd, unsigned int flag)
{
	unsigned int val;
	val = inl(cm->iobase + cmd);
	val &= ~flag;
	outl(val, cm->iobase + cmd);
}


/*
 * PCM interface
 */

/*
 * calculate frequency
 */
static struct {
	unsigned	rate;
	unsigned	lower;
	unsigned	upper;
	unsigned char	freq;
} rate_lookup[] =
{
	{ 5512,		(0 + 5512) / 2,		(5512 + 8000) / 2,	0 },
	{ 8000,		(5512 + 8000) / 2,	(8000 + 11025) / 2,	4 },
	{ 11025,	(8000 + 11025) / 2,	(11025 + 16000) / 2,	1 },
	{ 16000,	(11025 + 16000) / 2,	(16000 + 22050) / 2,	5 },
	{ 22050,	(16000 + 22050) / 2,	(22050 + 32000) / 2,	2 },
	{ 32000,	(22050 + 32000) / 2,	(32000 + 44100) / 2,	6 },
	{ 44100,	(32000 + 44100) / 2,	(44100 + 48000) / 2,	3 },
	{ 48000,	(44100 + 48000) /2,	48000,			7 }
};

static int freq_tbl[] = { 5512, 11025, 22050, 44100, 8000, 16000, 32000, 48000 };
static const unsigned sample_size[] = { 1, 2, 2, 4 };
static const unsigned sample_shift[] = { 0, 1, 1, 2 };

static unsigned int snd_cmipci_fixed_rate(unsigned int rate)
{
	int i, freq = 4;

	if (rate > 48000)
		rate = 48000;
	if (rate < 5512)
		rate = 5512;
	for (i = 0; i < sizeof(rate_lookup) / sizeof(rate_lookup[0]); i++) {
		if (rate > rate_lookup[i].lower && rate <= rate_lookup[i].upper) {
			freq = rate_lookup[i].freq;
			break;
	    	}
	}
	return freq;
}

static void snd_cmipci_dma_transfer_limit(cmipci_t *cm, snd_pcm_subchn_t *subchn)
{
	unsigned int mode = 0, size, size1;
	snd_pcm_runtime_t *runtime = subchn->runtime;

	if (snd_pcm_format_width(runtime->format.format) == 16)
		mode |= 0x02;
	if (runtime->format.voices > 1)
		mode |= 0x01;
	size = size1 = snd_pcm_lib_transfer_size(subchn);
	size >>= sample_shift[mode];
	while (size & 0xffff0000) {
		size1 >>= 1;
		size >>= 1;
	}
	snd_pcm_lib_set_buffer_size(subchn, size1);
}

/*
 * prepare playback/capture channel
 * channel to be used must have been set in rec->ch.
 */
static int snd_cmipci_pcm_prepare(cmipci_t *cm, cmipci_pcm_t *rec,
				 snd_pcm_subchn_t *subchn)
{
	unsigned long flags;
	unsigned int reg, freq, val;
	unsigned short count;
	snd_pcm_runtime_t *runtime = subchn->runtime;

	rec->dma_size = snd_pcm_lib_transfer_size(subchn);
	rec->frag_size = snd_pcm_lib_transfer_fragment(subchn);
	rec->fmt = 0;
	if (snd_pcm_format_width(runtime->format.format) == 16)
		rec->fmt |= 0x02;
	if (runtime->format.voices > 1)
		rec->fmt |= 0x01;
	rec->offset = virt_to_bus(runtime->dma_area->buf);

	spin_lock_irqsave(&cm->reg_lock, flags);

	/* set buffer address */
	reg = rec->ch ? CM_REG_CH1_FRAME1 : CM_REG_CH0_FRAME1;
	snd_cmipci_write(cm, reg, rec->offset);
	/* program sample counts */
	reg = rec->ch ? CM_REG_CH1_FRAME2 : CM_REG_CH0_FRAME2;
	count = (unsigned short)(rec->dma_size >> sample_shift[rec->fmt]) - 1;
	outw(count, cm->iobase + reg);
	count = (unsigned short)(rec->frag_size >> sample_shift[rec->fmt]) - 1;
	outw(count, cm->iobase + reg + 2);

	/* set adc/dac flag */
	val = rec->ch ? CM_CHADC1 : CM_CHADC0;
	if (rec->dac)
		cm->ctrl &= ~val;
	else
		cm->ctrl |= val;
	snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl);
	//snd_printd("cmipci: functrl0 = %08x\n", cm->ctrl);

	/* set sample rate */
	freq = snd_cmipci_fixed_rate(runtime->format.rate);
	val = snd_cmipci_read(cm, CM_REG_FUNCTRL1);
	if (rec->dac) {
		val &= ~CM_DSFC_MASK;
		val |= (freq << CM_DSFC_SHIFT) & CM_DSFC_MASK;
	} else {
		val &= ~CM_ASFC_MASK;
		val |= (freq << CM_ASFC_SHIFT) & CM_ASFC_MASK;
	}
	snd_cmipci_write(cm, CM_REG_FUNCTRL1, val);
	//snd_printd("cmipci: functrl1 = %08x\n", val);

	/* set format */
	val = snd_cmipci_read(cm, CM_REG_CHFORMAT);
	if (rec->ch) {
		val &= ~CM_CH1FMT_MASK;
		val |= rec->fmt << CM_CH1FMT_SHIFT;
	} else {
		val &= ~CM_CH0FMT_MASK;
		val |= rec->fmt << CM_CH0FMT_SHIFT;
	}
	snd_cmipci_write(cm, CM_REG_CHFORMAT, val);
	//snd_printd("cmipci: chformat = %08x\n", val);

	rec->running = 0;
	spin_unlock_irqrestore(&cm->reg_lock, flags);

	return 0;
}

/*
 * ioctl - change rate
 */
static int snd_cmipci_pcm_ioctl(cmipci_t *cm, snd_pcm_subchn_t *subchn,
			       unsigned int cmd, unsigned long *arg)
{
	if (cmd == SND_PCM_IOCTL1_PARAMS) {
		subchn->runtime->format.rate = freq_tbl[snd_cmipci_fixed_rate(subchn->runtime->format.rate)];
		snd_cmipci_dma_transfer_limit(cm, subchn);
	}
	return 0;
}

/*
 * PCM trigger/stop
 */
static int snd_cmipci_pcm_trigger(cmipci_t *cm, cmipci_pcm_t *rec,
				 snd_pcm_subchn_t *subchn, int cmd)
{
	unsigned long flags;
	unsigned int inthld, chen, reset;

	if (cmd == SND_PCM_TRIGGER_SYNC_GO)
		return -EINVAL;
	if (rec->ch) {
		inthld = CM_CH1_INT_EN;
		chen = CM_CHEN1;
		reset = CM_RST_CH1;
	} else {
		inthld = CM_CH0_INT_EN;
		chen = CM_CHEN0;
		reset = CM_RST_CH0;
	}

	spin_lock_irqsave(&cm->reg_lock, flags);
	if (cmd == SND_PCM_TRIGGER_GO) {
		rec->running = 1;
		/* set interrupt */
		snd_cmipci_set_bit(cm, CM_REG_INT_HLDCLR, inthld);
		cm->ctrl |= chen;
		/* enable channel */
		snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl);
		//snd_printd("cmipci: functrl0 = %08x\n", cm->ctrl);
	} else if (cmd == SND_PCM_TRIGGER_STOP) {
		rec->running = 0;
		/* disable interrupt */
		snd_cmipci_clear_bit(cm, CM_REG_INT_HLDCLR, inthld);
		/* reset */
		cm->ctrl &= ~chen;
		snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl | reset);
		snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl);
	}
	spin_unlock_irqrestore(&cm->reg_lock, flags);
	return 0;
}

/*
 * return the current pointer
 */
static unsigned int snd_cmipci_pcm_pointer(cmipci_t *cm, cmipci_pcm_t *rec,
					  snd_pcm_subchn_t *subchn)
{
	unsigned long flags;
	unsigned int ptr, reg;
	spin_lock_irqsave(&cm->reg_lock, flags);
	if (rec->running) {
		reg = rec->ch ? CM_REG_CH1_FRAME1 : CM_REG_CH0_FRAME1;
		ptr = snd_cmipci_read(cm, reg) - rec->offset;
	} else {
		ptr = 0;
	}
	spin_unlock_irqrestore(&cm->reg_lock, flags);
	return ptr;
}

/*
 * playback
 */

static int snd_cmipci_playback_prepare(void *private_data,
				      snd_pcm_subchn_t *subchn)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, private_data, -ENXIO);
	return snd_cmipci_pcm_prepare(cm, &cm->playback, subchn);
}

static int snd_cmipci_playback_ioctl(void *private_data,
				    snd_pcm_subchn_t *subchn,
				    unsigned int cmd,
				    unsigned long *arg)
{
	int result;
	cmipci_t *cm = snd_magic_cast(cmipci_t, private_data, -ENXIO);

	result = snd_pcm_lib_ioctl(private_data, subchn, cmd, arg);
	if (result < 0)
		return result;
	return snd_cmipci_pcm_ioctl(cm, subchn, cmd, arg);
}

static int snd_cmipci_playback_trigger(void *private_data,
				      snd_pcm_subchn_t *subchn,
				      int cmd)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, private_data, -ENXIO);
	return snd_cmipci_pcm_trigger(cm, &cm->playback, subchn, cmd);
}

static unsigned int snd_cmipci_playback_pointer(void *private_data,
					       snd_pcm_subchn_t *subchn)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, private_data, -ENXIO);
	return snd_cmipci_pcm_pointer(cm, &cm->playback, subchn);
}



/*
 * capture
 */

static int snd_cmipci_capture_prepare(void *private_data,
				      snd_pcm_subchn_t *subchn)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, private_data, -ENXIO);
	return snd_cmipci_pcm_prepare(cm, &cm->capture, subchn);
}

static int snd_cmipci_capture_ioctl(void *private_data,
				   snd_pcm_subchn_t *subchn,
				   unsigned int cmd,
				   unsigned long *arg)
{
	int result;
	cmipci_t *cm = snd_magic_cast(cmipci_t, private_data, -ENXIO);

	result = snd_pcm_lib_ioctl(private_data, subchn, cmd, arg);
	if (result < 0)
		return result;
	return snd_cmipci_pcm_ioctl(cm, subchn, cmd, arg);
}

static int snd_cmipci_capture_trigger(void *private_data,
				     snd_pcm_subchn_t *subchn,
				     int cmd)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, private_data, -ENXIO);
	return snd_cmipci_pcm_trigger(cm, &cm->capture, subchn, cmd);
}

static unsigned int snd_cmipci_capture_pointer(void *private_data,
					      snd_pcm_subchn_t *subchn)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, private_data, -ENXIO);
	return snd_cmipci_pcm_pointer(cm, &cm->capture, subchn);
}

/*
 * interrupt handler
 */
static void snd_cmipci_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	snd_card_cmipci_t *scard = (snd_card_cmipci_t *)dev_id;
	cmipci_t *cm;
	unsigned int status;
	
	if (scard == NULL)
		return;
	if ((cm = scard->cmipci) == NULL)
		return;

	/* fastpath out, to ease interrupt sharing */
	status = snd_cmipci_read(cm, CM_REG_INT_STATUS);
	if (!(status & CM_INTR))
		return;

	/* acknowledge interrupt */
	if (status & CM_CHINT0) {
		spin_lock(&cm->reg_lock);
		snd_cmipci_clear_bit(cm, CM_REG_INT_HLDCLR, CM_CH0_INT_EN);
		snd_cmipci_set_bit(cm, CM_REG_INT_HLDCLR, CM_CH0_INT_EN);
	}
	if (status & CM_CHINT1) {
		snd_cmipci_clear_bit(cm, CM_REG_INT_HLDCLR, CM_CH1_INT_EN);
		snd_cmipci_set_bit(cm, CM_REG_INT_HLDCLR, CM_CH1_INT_EN);
	}
	spin_unlock(&cm->reg_lock);

	if (cm->rmidi) {
		if (status & CM_UARTINT) {
			snd_mpu401_uart_interrupt(cm->rmidi);
		}
	}

	if (cm->pcm) {
		if ((status & CM_CHINT0) && cm->playback.running)
			snd_pcm_transfer_done(cm->playback.subchn);
		if ((status & CM_CHINT1) && cm->capture.running)
			snd_pcm_transfer_done(cm->capture.subchn);
	}
}

/*
 * mixer interface:
 * - CM8338/8738 has a compatible mixer interface with SB16, but
 *   lack of some elements like tone control, i/o gain and AGC.
 * - Access to native registers:
 *   - A 3D switch
 *   - Output mute switches
 */
static void snd_cmipci_mixer_write(cmipci_t *s, unsigned char idx, unsigned char data)
{
	outb(idx, s->iobase + CM_REG_SB16_ADDR);
	outb(data, s->iobase + CM_REG_SB16_DATA);
}

static unsigned char snd_cmipci_mixer_read(cmipci_t *s, unsigned char idx)
{
	unsigned char v;

	outb(idx, s->iobase + CM_REG_SB16_ADDR);
	v = inb(s->iobase + CM_REG_SB16_DATA);
	return v;
}

static int snd_cmipci_input_route(snd_kmixer_element_t * element,
				 int w_flag,
				 unsigned int *prsw,
				 unsigned char left_bit,
				 unsigned char right_bit)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char oleft, oright;
	int change = 0, tmp;

	left_bit = 1 << left_bit;
	right_bit = 1 << right_bit;
	spin_lock_irqsave(&cm->reg_lock, flags);
	oleft = snd_cmipci_mixer_read(cm, SB_DSP4_INPUT_LEFT);
	oright = snd_cmipci_mixer_read(cm, SB_DSP4_INPUT_RIGHT);
	if (!w_flag) {
		snd_mixer_set_bit(prsw, 0, oleft & left_bit);
		snd_mixer_set_bit(prsw, 1, oright & left_bit);
		snd_mixer_set_bit(prsw, 2, oleft & right_bit);
		snd_mixer_set_bit(prsw, 3, oright & right_bit);
	} else {
		tmp = snd_mixer_get_bit(prsw, 0);
		if (tmp != ((oleft & left_bit) != 0)) {
			change = 1;
			oleft &= ~left_bit;
			if (tmp)
				oleft |= left_bit;
		}
		tmp = snd_mixer_get_bit(prsw, 1);
		if (tmp != ((oright & left_bit) != 0)) {
			change = 1;
			oright &= ~left_bit;
			if (tmp)
				oright |= left_bit;
		}
		tmp = snd_mixer_get_bit(prsw, 2);
		if (tmp != ((oleft & right_bit) != 0)) {
			change = 1;
			oleft &= ~right_bit;
			if (tmp)
				oleft |= right_bit;
		}
		tmp = snd_mixer_get_bit(prsw, 3);
		if (tmp != ((oright & right_bit) != 0)) {
			change = 1;
			oright &= ~right_bit;
			if (tmp)
				oright |= right_bit;
		}
		snd_cmipci_mixer_write(cm, SB_DSP4_INPUT_LEFT, oleft);
		snd_cmipci_mixer_write(cm, SB_DSP4_INPUT_RIGHT, oright);
	}
	spin_unlock_irqrestore(&cm->reg_lock, flags);
	return change;
}

static int snd_cmipci_cd_input_switch(snd_kmixer_element_t *element, int w_flag, unsigned int *prsw)
{
	return snd_cmipci_input_route(element, w_flag, prsw, 2, 1);
}

static int snd_cmipci_line_input_switch(snd_kmixer_element_t *element, int w_flag, unsigned int *prsw)
{
	return snd_cmipci_input_route(element, w_flag, prsw, 4, 3);
}

static int snd_cmipci_midi_input_switch(snd_kmixer_element_t *element, int w_flag, unsigned int *prsw)
{
	return snd_cmipci_input_route(element, w_flag, prsw, 6, 5);
}

static int snd_cmipci_mic_input_switch(snd_kmixer_element_t *element, int w_flag, unsigned int *bitmap)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char lreg, rreg, oleft, oright;
	int change = 0;

	spin_lock_irqsave(&cm->reg_lock, flags);
	oleft = (lreg = snd_cmipci_mixer_read(cm, SB_DSP4_INPUT_LEFT)) & 1;
	oright = (rreg = snd_cmipci_mixer_read(cm, SB_DSP4_INPUT_RIGHT)) & 1;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, oleft);
		snd_mixer_set_bit(bitmap, 1, oright);
	} else {
		change = oleft != snd_mixer_get_bit(bitmap, 0) ||
		    oright != snd_mixer_get_bit(bitmap, 1);
		lreg &= ~1;
		lreg |= snd_mixer_get_bit(bitmap, 0) ? 1 : 0;
		rreg &= ~1;
		rreg |= snd_mixer_get_bit(bitmap, 1) ? 1 : 0;
		snd_cmipci_mixer_write(cm, SB_DSP4_INPUT_LEFT, lreg);
		snd_cmipci_mixer_write(cm, SB_DSP4_INPUT_RIGHT, rreg);
	}
	spin_unlock_irqrestore(&cm->reg_lock, flags);
	return change;
}

static int snd_cmipci_mic_output_switch(snd_kmixer_element_t *element, int w_flag, unsigned int *bitmap)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char old, oreg;
	int change = 0;

	spin_lock_irqsave(&cm->reg_lock, flags);
	old = (oreg = snd_cmipci_mixer_read(cm, SB_DSP4_OUTPUT_SW)) & 1;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, old);
	} else {
		change = old != snd_mixer_get_bit(bitmap, 0);
		oreg &= ~1;
		oreg |= snd_mixer_get_bit(bitmap, 0) ? 1 : 0;
		snd_cmipci_mixer_write(cm, SB_DSP4_OUTPUT_SW, oreg);
	}
	spin_unlock_irqrestore(&cm->reg_lock, flags);
	return change;
}

static int snd_cmipci_cd_output_switch(snd_kmixer_element_t *element, int w_flag, unsigned int *bitmap)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char oreg, oleft, oright;
	int change = 0;

	spin_lock_irqsave(&cm->reg_lock, flags);
	oreg = snd_cmipci_mixer_read(cm, SB_DSP4_OUTPUT_SW);
	oleft = (oreg & 4) ? 1 : 0;
	oright = (oreg & 2) ? 1 : 0;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, oleft);
		snd_mixer_set_bit(bitmap, 1, oright);
	} else {
		change = oleft != snd_mixer_get_bit(bitmap, 0) ||
		    oright != snd_mixer_get_bit(bitmap, 1);
		oreg &= ~6;
		oreg |= snd_mixer_get_bit(bitmap, 0) ? 4 : 0;
		oreg |= snd_mixer_get_bit(bitmap, 1) ? 2 : 0;
		snd_cmipci_mixer_write(cm, SB_DSP4_OUTPUT_SW, oreg);
	}
	spin_unlock_irqrestore(&cm->reg_lock, flags);
	return change;
}

static int snd_cmipci_line_output_switch(snd_kmixer_element_t *element, int w_flag, unsigned int *bitmap)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char oreg, oleft, oright;
	int change = 0;

	spin_lock_irqsave(&cm->reg_lock, flags);
	oreg = snd_cmipci_mixer_read(cm, SB_DSP4_OUTPUT_SW);
	oleft = (oreg & 0x10) ? 1 : 0;
	oright = (oreg & 8) ? 1 : 0;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, oleft);
		snd_mixer_set_bit(bitmap, 1, oright);
	} else {
		change = oleft != snd_mixer_get_bit(bitmap, 0) ||
		    oright != snd_mixer_get_bit(bitmap, 1);
		oreg &= ~0x18;
		oreg |= snd_mixer_get_bit(bitmap, 0) ? 0x10 : 0;
		oreg |= snd_mixer_get_bit(bitmap, 1) ? 8 : 0;
		snd_cmipci_mixer_write(cm, SB_DSP4_OUTPUT_SW, oreg);
	}
	spin_unlock_irqrestore(&cm->reg_lock, flags);
	return change;
}

static int snd_cmipci_volume_level(snd_kmixer_element_t *element,
				  int w_flag, int *voices,
				  unsigned char max,
				  unsigned char shift,
				  unsigned char reg)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char lreg, rreg, oleft, oright;
	int change = 0;

	spin_lock_irqsave(&cm->reg_lock, flags);
	oleft = ((lreg = snd_cmipci_mixer_read(cm, reg + 0)) >> shift) & max;
	oright = ((rreg = snd_cmipci_mixer_read(cm, reg + 1)) >> shift) & max;
	if (!w_flag) {
		voices[0] = oleft;
		voices[1] = oright;
	} else {
		change = oleft != voices[0] || oright != voices[1];
		lreg &= ~(max << shift);
		lreg |= voices[0] << shift;
		rreg &= ~(max << shift);
		rreg |= voices[1] << shift;
		snd_cmipci_mixer_write(cm, reg + 0, lreg);
		snd_cmipci_mixer_write(cm, reg + 1, rreg);
	}
	spin_unlock_irqrestore(&cm->reg_lock, flags);
	return change;

}

static int snd_cmipci_mono_volume_level(snd_kmixer_element_t *element,
				       int w_flag, int *voices,
				       unsigned char max,
				       unsigned char shift,
				       unsigned char reg)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char oreg, oval;
	int change = 0;

	spin_lock_irqsave(&cm->reg_lock, flags);
	oval = ((oreg = snd_cmipci_mixer_read(cm, reg)) >> shift) & max;
	if (!w_flag) {
		voices[0] = oval;
	} else {
		change = oval != voices[0];
		oreg &= ~(max << shift);
		oreg |= voices[0] << shift;
		snd_cmipci_mixer_write(cm, reg, oreg);
	}
	spin_unlock_irqrestore(&cm->reg_lock, flags);
	return change;

}

static int snd_cmipci_master_volume_level(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_cmipci_volume_level(element, w_flag, voices,
				      31, 3, SB_DSP4_MASTER_DEV);
}

static int snd_cmipci_pcm_volume_level(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_cmipci_volume_level(element, w_flag, voices,
				      31, 3, SB_DSP4_PCM_DEV);
}

static int snd_cmipci_synth_volume_level(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_cmipci_volume_level(element, w_flag, voices,
				      31, 3, SB_DSP4_SYNTH_DEV);
}

static int snd_cmipci_cd_volume_level(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_cmipci_volume_level(element, w_flag, voices,
				      31, 3, SB_DSP4_CD_DEV);
}

static int snd_cmipci_line_volume_level(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_cmipci_volume_level(element, w_flag, voices,
				      31, 3, SB_DSP4_LINE_DEV);
}

static int snd_cmipci_mic_volume_level(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_cmipci_mono_volume_level(element, w_flag, voices,
					   31, 3, SB_DSP4_MIC_DEV);
}

static int snd_cmipci_speaker_volume_level(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_cmipci_mono_volume_level(element, w_flag, voices,
					   3, 6, SB_DSP4_SPEAKER_DEV);
}

static int snd_cmipci_3d_surround_switch(snd_kmixer_element_t *element,
					int w_flag, int *value)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char old, oreg;
	int change = 0;

	spin_lock_irqsave(&cm->reg_lock, flags);
	oreg = inb(cm->iobase + CM_REG_MIXER1);
	old =  (oreg & CM_X3DEN) ? 1 : 0;
	if (!w_flag) {
		*value = old;
	} else {
		change = old != *value;
		oreg &= ~CM_X3DEN;
		oreg |= *value ? CM_X3DEN : 0;
		outb(oreg, cm->iobase + CM_REG_MIXER1);
	}
	spin_unlock_irqrestore(&cm->reg_lock, flags);
	return change;
}

static int snd_cmipci_mute_pcm(snd_kmixer_element_t *element,
			       int w_flag, int *value)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char old, oreg;
	int change = 0;

	spin_lock_irqsave(&cm->reg_lock, flags);
	oreg = inb(cm->iobase + CM_REG_MIXER1);
	old =  (oreg & CM_WSMUTE) ? 0 : 1;
	if (!w_flag) {
		*value = old;
	} else {
		change = old != *value;
		oreg &= ~CM_WSMUTE;
		oreg |= *value ? 0 : CM_WSMUTE;
		outb(oreg, cm->iobase + CM_REG_MIXER1);
	}
	spin_unlock_irqrestore(&cm->reg_lock, flags);
	return change;
}

static int snd_cmipci_pcm_input_switch(snd_kmixer_element_t *element,
				       int w_flag, unsigned int *bitmap)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char val, oleft, oright;
	int change = 0;

	spin_lock_irqsave(&cm->reg_lock, flags);
	val = inb(cm->iobase + CM_REG_MIXER1);
	oleft = (val & CM_WAVEINL) ? 1 : 0;
	oright = (val & CM_WAVEINR) ? 1 : 0;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, oleft);
		snd_mixer_set_bit(bitmap, 1, oright);
	} else {
		change = oleft != snd_mixer_get_bit(bitmap, 0) ||
		    oright != snd_mixer_get_bit(bitmap, 1);
		val &= ~(CM_WAVEINL|CM_WAVEINR);
		val |= snd_mixer_get_bit(bitmap, 0) ? CM_WAVEINL : 0;
		val |= snd_mixer_get_bit(bitmap, 1) ? CM_WAVEINR : 0;
		outb(val, cm->iobase + CM_REG_MIXER1);
	}
	spin_unlock_irqrestore(&cm->reg_lock, flags);
	return change;
}

#if 0
static int snd_cmipci_mute_synth(snd_kmixer_element_t *element,
			       int w_flag, int *value)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char old, oreg;
	int change = 0;

	spin_lock_irqsave(&cm->reg_lock, flags);
	oreg = inb(cm->iobase + CM_REG_MIXER1);
	old =  (oreg & CM_FMMUTE) ? 0 : 1;
	if (!w_flag) {
		*value = old;
	} else {
		change = old != *value;
		oreg &= ~CM_FMMUTE;
		oreg |= *value ? 0: CM_FMMUTE;
		outb(oreg, cm->iobase + CM_REG_MIXER1);
	}
	spin_unlock_irqrestore(&cm->reg_lock, flags);
	return change;
}
#endif

static int snd_cmipci_group_ctrl_stereo(snd_kmixer_group_t * group,
					snd_kmixer_file_t * file, int w_flag,
					snd_mixer_group_t * ugroup,
					snd_mixer_volume1_control_t * volume1,
					snd_kmixer_element_t * volume1_element,
					int max,
					snd_mixer_sw1_control_t * sw1_output,
					snd_kmixer_element_t * sw1_output_element,
					snd_mixer_sw1_control_t * sw1_input,
					snd_kmixer_element_t * sw1_input_element,
					snd_mixer_sw3_control_t * sw3_input,
					snd_kmixer_element_t * sw3_input_element,
					snd_mixer_sw2_control_t * sw2_output,
					snd_kmixer_element_t * sw2_output_element)
{
	int voices[2];
	unsigned int bitmap;
	int change = 0;
	
	if (!w_flag) {
		ugroup->caps = 0;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		if (volume1) {
			ugroup->caps |= SND_MIXER_GRPCAP_VOLUME;
			volume1(volume1_element, 0, voices);
			ugroup->volume.names.front_left = voices[0];
			ugroup->volume.names.front_right = voices[1];
			ugroup->min = 0;
			ugroup->max = max;
		}
		if (sw1_output) {
			ugroup->caps |= SND_MIXER_GRPCAP_MUTE;
			sw1_output(sw1_output_element, 0, &bitmap);
			ugroup->mute = 0;
			if (!snd_mixer_get_bit(&bitmap, 0))
				ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (!snd_mixer_get_bit(&bitmap, 1))
				ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		} else if (sw2_output) {
			int value;
			ugroup->caps |= SND_MIXER_GRPCAP_MUTE | SND_MIXER_GRPCAP_JOINTLY_MUTE;
			sw2_output(sw2_output_element, 0, &value);
			ugroup->mute = 0;
			if (!value)
				ugroup->mute |= SND_MIXER_CHN_MASK_STEREO;
		}
		if (sw1_input) {
			ugroup->caps |= SND_MIXER_GRPCAP_CAPTURE;
			sw1_input(sw1_input_element, 0, &bitmap);
			ugroup->capture = 0;
			if (snd_mixer_get_bit(&bitmap, 0))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (snd_mixer_get_bit(&bitmap, 1))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		} else if (sw3_input) {
			ugroup->caps |= SND_MIXER_GRPCAP_CAPTURE;
			sw3_input(sw3_input_element, 0, &bitmap);
			ugroup->capture = 0;
			if (snd_mixer_get_bit(&bitmap, 0))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (snd_mixer_get_bit(&bitmap, 3))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		}
	} else {
		if (volume1) {
			voices[0] = ugroup->volume.names.front_left & max;
			voices[1] = ugroup->volume.names.front_right & max;
			if (volume1(volume1_element, 1, voices) > 0) {
				snd_mixer_element_value_change(file, volume1_element, 0);
				change = 1;
			}
		}
		if (sw1_output) {
			bitmap = 0;
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_LEFT))
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_RIGHT))
				snd_mixer_set_bit(&bitmap, 1, 1);
			if (sw1_output(sw1_output_element, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, sw1_output_element, 0);
				change = 1;
			}
		} else if (sw2_output) {
			int value = 0;
			if ((ugroup->mute & SND_MIXER_CHN_MASK_STEREO) == 0)
				value = 1;
			if (sw2_output(sw2_output_element, 1, &value) > 0) {
				snd_mixer_element_value_change(file, sw2_output_element, 0);
				change = 1;
			}
		}
		if (sw1_input) {
			bitmap = 0;
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_LEFT)
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_RIGHT)
				snd_mixer_set_bit(&bitmap, 1, 1);
			if (sw1_input(sw1_input_element, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, sw1_input_element, 0);
				change = 1;
			}
		} else if (sw3_input) {
			bitmap = 0;
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_LEFT)
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_RIGHT)
				snd_mixer_set_bit(&bitmap, 3, 1);
			if (sw3_input(sw3_input_element, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, sw3_input_element, 0);
				change = 1;
			}
		}
	}
	return change;
}

static int snd_cmipci_group_ctrl_mono(snd_kmixer_group_t * group,
				     snd_kmixer_file_t * file, int w_flag,
				     snd_mixer_group_t * ugroup,
				     snd_mixer_volume1_control_t * volume1,
				     snd_kmixer_element_t * volume1_element,
				     int max,
				     snd_mixer_sw1_control_t * sw1_output,
				     snd_kmixer_element_t * sw1_output_element,
				     snd_mixer_sw1_control_t * sw1_input,
				     snd_kmixer_element_t * sw1_input_element)
{
	int voice;
	unsigned int bitmap;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = 0;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		if (volume1) {
			ugroup->caps |= SND_MIXER_GRPCAP_VOLUME | SND_MIXER_GRPCAP_JOINTLY_VOLUME;
			volume1(volume1_element, 0, &voice);
			ugroup->volume.names.front_left = voice;
			ugroup->volume.names.front_right = voice;
			ugroup->min = 0;
			ugroup->max = max;
		}
		if (sw1_output) {
			ugroup->caps |= SND_MIXER_GRPCAP_MUTE;
			sw1_output(sw1_output_element, 0, &bitmap);
			ugroup->mute = 0;
			if (!snd_mixer_get_bit(&bitmap, 0))
				ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (!snd_mixer_get_bit(&bitmap, 1))
				ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		}
		if (sw1_input) {
			ugroup->caps |= SND_MIXER_GRPCAP_CAPTURE;
			sw1_input(sw1_input_element, 0, &bitmap);
			ugroup->capture = 0;
			if (snd_mixer_get_bit(&bitmap, 0))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (snd_mixer_get_bit(&bitmap, 1))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		}
	} else {
		if (volume1) {
			voice = ugroup->volume.names.front_left & max;
			if (volume1(volume1_element, 1, &voice) > 0) {
				snd_mixer_element_value_change(file, volume1_element, 0);
				change = 1;
			}
		}
		if (sw1_output) {
			bitmap = 0;
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_LEFT))
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_RIGHT))
				snd_mixer_set_bit(&bitmap, 1, 1);
			if (sw1_output(sw1_output_element, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, sw1_output_element, 0);
				change = 1;
			}
		}
		if (sw1_input) {
			bitmap = 0;
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_LEFT)
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_RIGHT)
				snd_mixer_set_bit(&bitmap, 1, 1);
			if (sw1_input(sw1_input_element, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, sw1_input_element, 0);
				change = 1;
			}
		}
	}
	return change;
}

static int snd_cmipci_group_speaker(snd_kmixer_group_t * group,
				   snd_kmixer_file_t * file, int w_flag,
				   snd_mixer_group_t * ugroup)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, group->private_data, -ENXIO);

	return snd_cmipci_group_ctrl_mono(group, file, w_flag, ugroup,
					 snd_cmipci_speaker_volume_level,
					 cm->me_vol_speaker,
					 3,
					 NULL,
					 NULL,
					 NULL,
					 NULL);
}

static int snd_cmipci_group_mic(snd_kmixer_group_t * group,
			       snd_kmixer_file_t * file, int w_flag,
			       snd_mixer_group_t * ugroup)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, group->private_data, -ENXIO);

	return snd_cmipci_group_ctrl_mono(group, file, w_flag, ugroup,
					 snd_cmipci_mic_volume_level,
					 cm->me_vol_mic,
					 31,
					 snd_cmipci_mic_output_switch,
					 cm->me_sw1_mic_output,
					 snd_cmipci_mic_input_switch,
					 cm->me_sw1_mic_input);
}

static int snd_cmipci_group_line(snd_kmixer_group_t * group,
				snd_kmixer_file_t * file, int w_flag,
				snd_mixer_group_t * ugroup)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, group->private_data, -ENXIO);
            
	return snd_cmipci_group_ctrl_stereo(group, file, w_flag, ugroup,
					    snd_cmipci_line_volume_level,
					    cm->me_vol_line,
					    31,
					    snd_cmipci_line_output_switch,
					    cm->me_sw1_line_output,
					    NULL,
					    NULL,
					    snd_cmipci_line_input_switch,
					    cm->me_sw3_line_input,
					    NULL, NULL);
}

static int snd_cmipci_group_cd(snd_kmixer_group_t * group,
			      snd_kmixer_file_t * file, int w_flag,
			      snd_mixer_group_t * ugroup)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, group->private_data, -ENXIO);

	return snd_cmipci_group_ctrl_stereo(group, file, w_flag, ugroup,
					    snd_cmipci_cd_volume_level,
					    cm->me_vol_cd,
					    31,
					    snd_cmipci_cd_output_switch,
					    cm->me_sw1_cd_output,
					    NULL,
					    NULL,
					    snd_cmipci_cd_input_switch,
					    cm->me_sw3_cd_input,
					    NULL, NULL);
}

static int snd_cmipci_group_synth(snd_kmixer_group_t * group,
				 snd_kmixer_file_t * file, int w_flag,
				 snd_mixer_group_t * ugroup)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, group->private_data, -ENXIO);

	return snd_cmipci_group_ctrl_stereo(group, file, w_flag, ugroup,
					    snd_cmipci_synth_volume_level,
					    cm->me_vol_synth,
					    31,
					    NULL,
					    NULL,
					    NULL,
					    NULL,
					    snd_cmipci_midi_input_switch,
					    cm->me_sw3_synth_input,
					    NULL, NULL);
}

static int snd_cmipci_group_pcm(snd_kmixer_group_t * group,
				   snd_kmixer_file_t * file, int w_flag,
				   snd_mixer_group_t * ugroup)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, group->private_data, -ENXIO);

	return snd_cmipci_group_ctrl_stereo(group, file, w_flag, ugroup,
					    snd_cmipci_pcm_volume_level,
					    cm->me_vol_pcm,
					    31,
					    NULL,
					    NULL,
					    snd_cmipci_pcm_input_switch,
					    cm->me_sw1_pcm_input,
					    NULL,
					    NULL,
					    snd_cmipci_mute_pcm,
					    cm->me_sw2_mute_pcm);
}

static int snd_cmipci_group_master(snd_kmixer_group_t * group,
				  snd_kmixer_file_t * file, int w_flag,
				  snd_mixer_group_t * ugroup)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, group->private_data, -ENXIO);

	return snd_cmipci_group_ctrl_stereo(group, file, w_flag, ugroup,
					    snd_cmipci_master_volume_level,
					    cm->me_vol_master,
					    31,
					    NULL,
					    NULL,
					    NULL,
					    NULL,
					    NULL,
					    NULL,
					    NULL, NULL);
}

static int __init snd_cmipci_mixer_new(cmipci_t *cm, int device, int pcm_device, snd_kmixer_t **rmixer)
{
	snd_kmixer_t *mixer;
	unsigned long flags;
	snd_kmixer_group_t *group;
	int err;
	static struct snd_mixer_element_volume1_range db_range1[2] =
	{
		{0, 31, -6200, 0},
		{0, 31, -6200, 0}
	};
	static struct snd_mixer_element_volume1_range db_range2[1] =
	{
		{0, 3, -1800, 0}
	};
	static snd_mixer_voice_t stereo_voices[2] =
	{
		{SND_MIXER_VOICE_LEFT, 0},
		{SND_MIXER_VOICE_RIGHT, 0}
	};

	snd_debug_check(rmixer == NULL, -EINVAL);
	*rmixer = NULL;
	if ((err = snd_mixer_new(cm->card, "CMedia PCI", device, &mixer)) < 0)
		return err;
	strcpy(mixer->name, mixer->id);

	spin_lock_irqsave(&cm->reg_lock, flags);
	snd_cmipci_mixer_write(cm, 0x00, 0x00);		/* mixer reset */
	spin_unlock_irqrestore(&cm->reg_lock, flags);

	/* build input and output accumulator */
	if ((cm->me_in_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_INPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	if ((cm->me_out_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	/* build master volume control */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_OSS_VOLUME, snd_cmipci_group_master, cm)) == NULL)
		goto __error;
	if ((cm->me_vol_master = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, db_range1, snd_cmipci_master_volume_level, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_vol_master) < 0)
		goto __error;
	/* 3d Stereo Enhancement switch */
	if ((cm->me_sw1_3dse = snd_mixer_lib_sw2(mixer, "3D Surround", 0, snd_cmipci_3d_surround_switch, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_sw1_3dse) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_out_accu, cm->me_sw1_3dse) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_sw1_3dse, cm->me_vol_master) < 0)
		goto __error;
	if ((cm->me_out_master = snd_mixer_lib_io_stereo(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_vol_master, cm->me_out_master) < 0)
		goto __error;
	/* PCM */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_PCM, 0, SND_MIXER_OSS_PCM, snd_cmipci_group_pcm, cm)) == NULL)
		goto __error;
	if ((cm->me_playback = snd_mixer_lib_pcm1(mixer, SND_MIXER_ELEMENT_PLAYBACK, 0, SND_MIXER_ETYPE_PLAYBACK1, 1, &pcm_device)) == NULL)
		goto __error;
	if ((cm->me_vol_pcm = snd_mixer_lib_volume1(mixer, "PCM Volume", 0, 2, db_range1, snd_cmipci_pcm_volume_level, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_vol_pcm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_playback, cm->me_vol_pcm) < 0)
		goto __error;
	if ((cm->me_sw2_mute_pcm = snd_mixer_lib_sw2(mixer, "PCM Output Switch", 0, snd_cmipci_mute_pcm, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_sw2_mute_pcm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_vol_pcm, cm->me_sw2_mute_pcm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_sw2_mute_pcm, cm->me_out_accu) < 0)
		goto __error;
	if ((cm->me_sw1_pcm_input = snd_mixer_lib_sw1(mixer, "PCM Input Switch", 0, 2, snd_cmipci_pcm_input_switch, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_sw1_pcm_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_vol_pcm, cm->me_sw1_pcm_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_sw1_pcm_input, cm->me_in_accu) < 0)
		goto __error;
	/* Synth */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_OSS_SYNTH, snd_cmipci_group_synth, cm)) == NULL)
		goto __error;
	if ((cm->me_in_synth = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((cm->me_vol_synth = snd_mixer_lib_volume1(mixer, "Synth Volume", 0, 2, db_range1, snd_cmipci_synth_volume_level, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_vol_synth) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_vol_synth, cm->me_out_accu) < 0)
		goto __error;
	if ((cm->me_sw3_synth_input = snd_mixer_lib_sw3(mixer, "Synth Input Switch", 0, SND_MIXER_SWITCH3_FULL_FEATURED, 2, stereo_voices, snd_cmipci_midi_input_switch, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_sw3_synth_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_vol_synth, cm->me_sw3_synth_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_sw3_synth_input, cm->me_in_accu) < 0)
		goto __error;
	/* CD */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_OSS_CD, snd_cmipci_group_cd, cm)) == NULL)
		goto __error;
	if ((cm->me_in_cd = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((cm->me_vol_cd = snd_mixer_lib_volume1(mixer, "CD Volume", 0, 2, db_range1, snd_cmipci_cd_volume_level, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_vol_cd) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_in_cd, cm->me_vol_cd) < 0)
		goto __error;
	if ((cm->me_sw1_cd_output = snd_mixer_lib_sw1(mixer, "CD Output Switch", 0, 2, snd_cmipci_cd_output_switch, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_sw1_cd_output) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_vol_cd, cm->me_sw1_cd_output) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_sw1_cd_output, cm->me_out_accu) < 0)
		goto __error;
	if ((cm->me_sw3_cd_input = snd_mixer_lib_sw3(mixer, "CD Input Switch", 0, SND_MIXER_SWITCH3_FULL_FEATURED, 2, stereo_voices, snd_cmipci_cd_input_switch, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_sw3_cd_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_vol_cd, cm->me_sw3_cd_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_sw3_cd_input, cm->me_in_accu) < 0)
		goto __error;
	/* Line */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_OSS_LINE, snd_cmipci_group_line, cm)) == NULL)
		goto __error;
	if ((cm->me_in_line = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((cm->me_vol_line = snd_mixer_lib_volume1(mixer, "Line Volume", 0, 2, db_range1, snd_cmipci_line_volume_level, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_vol_line) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_in_line, cm->me_vol_line) < 0)
		goto __error;
	if ((cm->me_sw1_line_output = snd_mixer_lib_sw1(mixer, "Line Output Switch", 0, 2, snd_cmipci_line_output_switch, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_sw1_line_output) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_vol_line, cm->me_sw1_line_output) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_sw1_line_output, cm->me_out_accu) < 0)
		goto __error;
	if ((cm->me_sw3_line_input = snd_mixer_lib_sw3(mixer, "Line Input Switch", 0, SND_MIXER_SWITCH3_FULL_FEATURED, 2, stereo_voices, snd_cmipci_line_input_switch, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_sw3_line_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_vol_line, cm->me_sw3_line_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_sw3_line_input, cm->me_in_accu) < 0)
		goto __error;
	/* MIC */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_OSS_MIC, snd_cmipci_group_mic, cm)) == NULL)
		goto __error;
	if ((cm->me_in_mic = snd_mixer_lib_io_mono(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((cm->me_vol_mic = snd_mixer_lib_volume1(mixer, "MIC Volume", 0, 1, db_range1, snd_cmipci_mic_volume_level, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_vol_mic) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_in_mic, cm->me_vol_mic) < 0)
		goto __error;
	if ((cm->me_sw1_mic_output = snd_mixer_lib_sw1(mixer, "MIC Output Switch", 0, 1, snd_cmipci_mic_output_switch, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_sw1_mic_output) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_vol_mic, cm->me_sw1_mic_output) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_sw1_mic_output, cm->me_out_accu) < 0)
		goto __error;
	if ((cm->me_sw1_mic_input = snd_mixer_lib_sw1(mixer, "MIC Input Switch", 0, 2, snd_cmipci_mic_input_switch, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_sw1_mic_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_vol_mic, cm->me_sw1_mic_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_sw1_mic_input, cm->me_in_accu) < 0)
		goto __error;
	/* Speaker */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_SPEAKER, 0, SND_MIXER_OSS_SPEAKER, snd_cmipci_group_speaker, cm)) == NULL)
		goto __error;
	if ((cm->me_in_speaker = snd_mixer_lib_io_mono(mixer, SND_MIXER_IN_SPEAKER, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((cm->me_vol_speaker = snd_mixer_lib_volume1(mixer, "PC Speaker Volume", 0, 1, db_range2, snd_cmipci_speaker_volume_level, cm)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, cm->me_vol_speaker) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_in_speaker, cm->me_vol_speaker) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_vol_speaker, cm->me_out_accu) < 0)
		goto __error;
	/* Capture endpoint */
	if ((cm->me_capture = snd_mixer_lib_pcm1(mixer, SND_MIXER_ELEMENT_CAPTURE, 0, SND_MIXER_ETYPE_CAPTURE1, 1, &pcm_device)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cm->me_in_accu, cm->me_capture) < 0)
		goto __error;

	*rmixer = cm->kmixer = mixer;
	return 0;

      __error:
	snd_device_free(cm->card, mixer);
	return -ENOMEM;
}

/*
 */
static snd_pcm_hardware_t snd_cmipci_playback =
{
	chninfo:	(SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_STREAM |
			 SND_PCM_CHNINFO_BLOCK | SND_PCM_CHNINFO_INTERLEAVE |
			 SND_PCM_CHNINFO_BLOCK_TRANSFER |
			 SND_PCM_CHNINFO_MMAP_VALID),
	formats:	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,
	rates:		SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_48000,
	min_rate:	4000,
	max_rate:	48000,
	min_voices:	1,
	max_voices:	2,
	min_fragment_size:	4096, // ??
	max_fragment_size:	(128*1024),
	fragment_align:	4095,
	fifo_size:	0,
	transfer_block_size:	4,
	ioctl:		snd_cmipci_playback_ioctl,
	prepare:	snd_cmipci_playback_prepare,
	trigger:	snd_cmipci_playback_trigger,
	pointer:	snd_cmipci_playback_pointer,
};

static snd_pcm_hardware_t snd_cmipci_capture =
{
	chninfo:	(SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_STREAM |
			 SND_PCM_CHNINFO_BLOCK | SND_PCM_CHNINFO_INTERLEAVE |
			 SND_PCM_CHNINFO_BLOCK_TRANSFER |
			 SND_PCM_CHNINFO_MMAP_VALID),
	formats:	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,
	rates:		SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_48000,
	min_rate:	4000,
	max_rate:	48000,
	min_voices:	1,
	max_voices:	2,
	min_fragment_size:	64,
	max_fragment_size:	(128*1024),
	fragment_align:	31,
	fifo_size:	0,
	transfer_block_size:	4,
	ioctl:		snd_cmipci_capture_ioctl,
	prepare:	snd_cmipci_capture_prepare,
	trigger:	snd_cmipci_capture_trigger,
	pointer:	snd_cmipci_capture_pointer,
};

static int snd_cmipci_playback_open(void *private_data,
				   snd_pcm_subchn_t * subchn)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, private_data, -ENXIO);
	int err;

	if ((err = snd_pcm_dma_alloc(subchn, cm->dma1ptr,
				     "C-Media PCI - DAC")) < 0)
		return err;
	cm->playback.subchn = subchn;
	cm->playback.ch = 0;
	cm->playback.dac = 1;
	subchn->runtime->hw = &snd_cmipci_playback;
	snd_pcm_set_mixer(subchn, cm->kmixer->device, cm->me_playback);

	return 0;
}

static int snd_cmipci_capture_open(void *private_data,
				  snd_pcm_subchn_t * subchn)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, private_data, -ENXIO);
	int err;

	if ((err = snd_pcm_dma_alloc(subchn, cm->dma2ptr,
				     "C-Media PCI - ADC")) < 0)
		return err;
	cm->capture.subchn = subchn;
	cm->capture.ch = 1;
	cm->capture.dac = 0;
	subchn->runtime->hw = &snd_cmipci_capture;
	snd_pcm_set_mixer(subchn, cm->kmixer->device, cm->me_capture);
	return 0;
}

static int snd_cmipci_playback_close(void *private_data,
				    snd_pcm_subchn_t * subchn)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, private_data, -ENXIO);

	cm->playback.subchn = NULL;
	snd_pcm_dma_free(subchn);
	return 0;
}

static int snd_cmipci_capture_close(void *private_data,
				   snd_pcm_subchn_t * subchn)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, private_data, -ENXIO);

	cm->capture.subchn = NULL;
	snd_pcm_dma_free(subchn);
	return 0;
}

static void snd_cmipci_pcm_free(void *private_data)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, private_data, );
	cm->pcm = NULL;
}

static int __init snd_cmipci_pcm_new(cmipci_t *cm, int device, snd_pcm_t **rpcm)
{
	snd_pcm_t *pcm;
	int err;

	*rpcm = NULL;
	err = snd_pcm_new(cm->card, "C-Media PCI", device, 1, 1, &pcm);
	if (err < 0)
		return err;

	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].private_data = cm;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].open = snd_cmipci_playback_open;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].close = snd_cmipci_playback_close;

	pcm->chn[SND_PCM_CHANNEL_CAPTURE].private_data = cm;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].open = snd_cmipci_capture_open;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].close = snd_cmipci_capture_close;

	pcm->private_data = cm;
	pcm->private_free = snd_cmipci_pcm_free;
	pcm->info_flags = SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_CAPTURE |
			  SND_PCM_INFO_DUPLEX;
	strcpy(pcm->name, "C-Media PCI DAC/ADC");
	*rpcm = cm->pcm = pcm;
	return 0;
}


/*
 * universal switches
 */

static int snd_cmipci_get_switch(snd_card_t *card,
				 snd_kswitch_t *kswitch,
				 snd_switch_t *uswitch,
				 unsigned int reg, unsigned int mask,
				 int is_byte)
{
	unsigned long flags;
	unsigned int val;
	cmipci_t *cm = snd_magic_cast(cmipci_t, kswitch->private_data, -ENXIO);

	spin_lock_irqsave(&cm->reg_lock, flags);
	if (is_byte)
		val = inb(cm->iobase + reg);
	else
		val = snd_cmipci_read(cm, reg);
	spin_unlock_irqrestore(&cm->reg_lock, flags);

	uswitch->type = SND_SW_TYPE_BOOLEAN;
	uswitch->value.enable = (val & mask) ? 1 : 0;
	return 0;
}

static int snd_cmipci_set_switch(snd_card_t *card,
				 snd_kswitch_t *kswitch,
				 snd_switch_t *uswitch,
				 unsigned int reg, unsigned int mask,
				 int is_byte)
{
	unsigned long flags;
	unsigned int val;
	int change;
	cmipci_t *cm = snd_magic_cast(cmipci_t, kswitch->private_data, -ENXIO);

	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	spin_lock_irqsave(&cm->reg_lock, flags);
	if (is_byte)
		val = inb(cm->iobase + reg);
	else
		val = snd_cmipci_read(cm, reg);
	change = (val & mask) != (uswitch->value.enable ? mask : 0);
	val &= ~mask;
	if (uswitch->value.enable)
		val |= mask;
	if (is_byte)
		outb((unsigned char)val, cm->iobase + reg);
	else
		snd_cmipci_write(cm, reg, val);
	spin_unlock_irqrestore(&cm->reg_lock, flags);
	return change;
}

#define DEFINE_BIT_SWITCH(str, sname, reg, mask, is_byte) \
static int snd_cmipci_get_switch_##sname(snd_card_t *card, snd_kswitch_t *ksw, snd_switch_t *usw)\
{ return snd_cmipci_get_switch(card, ksw, usw, reg, mask, is_byte); }\
static int snd_cmipci_set_switch_##sname(snd_card_t *card, snd_kswitch_t *ksw, snd_switch_t *usw)\
{ return snd_cmipci_set_switch(card, ksw, usw, reg, mask, is_byte); }\
static snd_kswitch_t snd_cmipci_switch_##sname = {\
						  name: str,\
						  get: (snd_get_switch_t*)snd_cmipci_get_switch_##sname,\
						  set: (snd_set_switch_t*)snd_cmipci_set_switch_##sname,\
}

DEFINE_BIT_SWITCH(SND_CTL_SW_JOYSTICK, joy,
		  CM_REG_FUNCTRL1, CM_JYSTK_EN, 0);
DEFINE_BIT_SWITCH("SPDIF In", spdifin,
		  CM_REG_FUNCTRL1, CM_SPDF_1, 0 );
DEFINE_BIT_SWITCH("SPDIF Out", spdifout,
		  CM_REG_LEGACY_CTRL, CM_ENSPDOUT|CM_SPDCOPYRHT|CM_DAC2SPDO, 0 );
DEFINE_BIT_SWITCH("SPDIF 5V", spdo5v,
		  CM_REG_MISC_CTRL, CM_SPDO5V, 0 );
DEFINE_BIT_SWITCH("SPDIF Loop", spdifloop,
		  CM_REG_FUNCTRL1, CM_SPDFLOOP, 0);
DEFINE_BIT_SWITCH("Four Channel", fourch,
		  CM_REG_MISC_CTRL, CM_N4SPK3D, 0);
DEFINE_BIT_SWITCH("Rear Out", rearout,
		  CM_REG_MIXER1, CM_SPK4, 1);

static void snd_cmipci_switches_new(cmipci_t *cm)
{
	snd_control_switch_new(cm->card, &snd_cmipci_switch_joy, cm);
	if (cm->card->type == SND_CARD_TYPE_CMI8738) {
		snd_control_switch_new(cm->card, &snd_cmipci_switch_spdifin, cm);
		snd_control_switch_new(cm->card, &snd_cmipci_switch_spdifout, cm);
		snd_control_switch_new(cm->card, &snd_cmipci_switch_spdo5v, cm);
		snd_control_switch_new(cm->card, &snd_cmipci_switch_spdifloop, cm);
		snd_control_switch_new(cm->card, &snd_cmipci_switch_fourch, cm);
		snd_control_switch_new(cm->card, &snd_cmipci_switch_rearout, cm);
	}
}


/*
 * proc interface
 */

static void snd_cmipci_proc_read(snd_info_buffer_t *buffer, void *private_data)
{
	cmipci_t *cm = snd_magic_cast(cmipci_t, private_data, );
	int i;
	
	snd_iprintf(buffer, "C-Media PCI\n\n");
	for (i = 0; i < 0x40; i++) {
		int v = inb(cm->iobase + i);
		if (i % 4 == 0)
			snd_iprintf(buffer, "%02x: ", i);
		snd_iprintf(buffer, "%02x", v);
		if (i % 4 == 3)
			snd_iprintf(buffer, "\n");
		else
			snd_iprintf(buffer, " ");
	}
}

static void __init snd_cmipci_proc_init(cmipci_t *cm)
{
	snd_info_entry_t *entry;

	if ((entry = snd_info_create_entry(cm->card, "cmipci")) != NULL) {
		entry->private_data = cm;
		entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
		entry->t.text.read_size = 256;
		entry->t.text.read = snd_cmipci_proc_read;
		if (snd_info_register(entry) < 0) {
			snd_info_free_entry(entry);
			entry = NULL;
		}
	}
	cm->proc_entry = entry;
}

static void snd_cmipci_proc_done(cmipci_t *cm)
{
	if (cm->proc_entry) {
		snd_info_unregister(cm->proc_entry);
		cm->proc_entry = NULL;
	}
}


#ifndef PCI_VENDOR_ID_CMEDIA
#define PCI_VENDOR_ID_CMEDIA         0x13F6
#endif
#ifndef PCI_DEVICE_ID_CMEDIA_CM8338A
#define PCI_DEVICE_ID_CMEDIA_CM8338A 0x0100
#endif
#ifndef PCI_DEVICE_ID_CMEDIA_CM8338B
#define PCI_DEVICE_ID_CMEDIA_CM8338B 0x0101
#endif
#ifndef PCI_DEVICE_ID_CMEDIA_CM8738
#define PCI_DEVICE_ID_CMEDIA_CM8738  0x0111
#endif

static struct pci_device_id snd_cmipci_ids[] __devinitdata = {
	{PCI_VENDOR_ID_CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8338A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
	{PCI_VENDOR_ID_CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8338B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
	{PCI_VENDOR_ID_CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8738, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
	{0,},
};


static int snd_cmipci_free(cmipci_t *cm)
{
	snd_cmipci_proc_done(cm);

	snd_cmipci_write(cm, CM_REG_INT_HLDCLR, 0);  /* disable ints */
	snd_cmipci_write(cm, CM_REG_FUNCTRL0, 0); /* disable channels */

	/* reset mixer */
	snd_cmipci_mixer_write(cm, 0, 0);

	synchronize_irq();
	snd_magic_kfree(cm);
	return 0;
}

static int __init snd_cmipci_create(snd_card_t *card, snd_card_cmipci_t *scard, cmipci_t **rcmipci)
{
	cmipci_t *cm;
	int err;
	static snd_device_ops_t ops = {
		(snd_dev_free_t *)snd_cmipci_free,
		NULL,
		NULL
	};
	unsigned int val;
	
	*rcmipci = NULL;

	cm = snd_magic_kcalloc(cmipci_t, 0, GFP_KERNEL);
	if (cm == NULL)
		return -ENOMEM;

	spin_lock_init(&cm->reg_lock);
	cm->card = card;
	cm->pci = scard->pci;
	cm->irqptr = scard->irqptr;
	cm->iobase = scard->iobase;
	cm->iomidi = scard->iomidi;
	cm->iosynth = scard->iosynth;

	cm->ctrl = CM_CHADC1;	/* FUNCNTRL0 */
	cm->dma1ptr = scard->dma1ptr;
	cm->dma2ptr = scard->dma2ptr;

	pci_set_master(cm->pci);

	/* initialize codec registers */
	snd_cmipci_write(cm, CM_REG_INT_HLDCLR, 0);   /* disable ints */
	snd_cmipci_write(cm, CM_REG_FUNCTRL0, 0); 	/* disable channels */
	snd_cmipci_write(cm, CM_REG_FUNCTRL1, 0);

	snd_cmipci_write(cm, CM_REG_CHFORMAT, 0x00200000);

	/* XXX */
	outb(0xee, cm->iobase + 0x26);

	/* set MPU address */
	if (scard->enable_midi) {
		switch (cm->iomidi) {
		case 0x320: val = CM_VMPU_320; break;
		case 0x310: val = CM_VMPU_310; break;
		case 0x300: val = CM_VMPU_300; break;
		default: val = CM_VMPU_330; break; /* 0x330 */
		}
		snd_cmipci_write(cm, CM_REG_LEGACY_CTRL, val);
	}

	/* enable FM */
	if (scard->enable_synth)
		snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_FM_EN);

	/* reset mixer */
	snd_cmipci_mixer_write(cm, 0, 0);

	/* create switches */
	snd_cmipci_switches_new(cm);

	synchronize_irq();

	snd_cmipci_proc_init(cm);

	if ((err = snd_device_new(card, SND_DEV_LOWLEVEL, cm, 0, &ops, NULL)) < 0) {
		snd_cmipci_free(cm);
		return err;
	}


	*rcmipci = cm;
	return 0;
}

/*
 */

MODULE_DEVICE_TABLE(pci, snd_cmipci_ids);

static void snd_cmipci_use_inc(snd_card_t *card)
{
	MOD_INC_USE_COUNT;
}

static void snd_cmipci_use_dec(snd_card_t *card)
{
	MOD_DEC_USE_COUNT;
}

static void snd_cmipci_card_free(void *private_data)
{
	if (private_data)
		snd_kfree(private_data);
}

static int __init snd_cmipci_probe(struct pci_dev *pci,
				  const struct pci_device_id *id)
{
	static int dev = 0;
	snd_card_t *card;
	snd_card_cmipci_t *scard;
	cmipci_t *cm = NULL;
	snd_pcm_t *pcm = NULL;
	snd_kmixer_t *mixer = NULL;
	snd_rawmidi_t *rmidi = NULL;
	int err;

	for (; dev < SND_CARDS; dev++) {
		if (! snd_enable[dev]) {
			dev++;
			return -ENOENT;
		}
		break;
	}
	if (dev >= SND_CARDS)
		return -ENODEV;

	scard = snd_kcalloc(sizeof(snd_card_cmipci_t), GFP_KERNEL);
	if (scard == NULL)
		return -ENOMEM;
	card = snd_card_new(snd_index[dev], snd_id[dev],
			    snd_cmipci_use_inc, snd_cmipci_use_dec);
	if (card == NULL) {
		snd_kfree(scard);
		return -ENOMEM;
	}
	card->private_data = scard;
	card->private_free = snd_cmipci_card_free;
	if ((err = pci_enable_device(pci)) < 0)
		goto __nodev;
	
	if (pci->device == PCI_DEVICE_ID_CMEDIA_CM8738)
		card->type = SND_CARD_TYPE_CMI8738;
	else
		card->type = SND_CARD_TYPE_CMI8338;

	scard->pci = pci;
	scard->iobase = pci_resource_start(pci, 0);

	scard->enable_midi = snd_enable_midi[dev];
	scard->iomidi = snd_mpu_port[dev];
	scard->enable_synth = snd_enable_fm[dev];
	scard->iosynth = snd_fm_port[dev];

	if (snd_register_ioport(card, pci_resource_start(pci, 0),
				CM_EXTENT_CODEC, "C-Media PCI", NULL) < 0) {
		err = -EBUSY;
		goto __nodev;
	}
	if ((err = snd_register_interrupt(card,
					  "C-Media PCI", scard->pci->irq,
					  SND_IRQ_TYPE_PCI, snd_cmipci_interrupt,
					  scard, NULL, &scard->irqptr)) < 0)
		goto __nodev;
	if ((err = snd_register_dma_channel(card,
					    "C-Media PCI - DAC1 frame", 0,
					    SND_DMA_TYPE_PCI, snd_dac_frame_size[dev],
					    NULL, &scard->dma1ptr)) < 0)
		goto __nodev;
	if ((err = snd_register_dma_channel(card,
					    "C-Media PCI - ADC frame", 2,
					    SND_DMA_TYPE_PCI, snd_adc_frame_size[dev],
					    NULL, &scard->dma2ptr)) < 0)
		goto __nodev;


	if (scard->enable_midi) {
		if (snd_register_ioport(card, scard->iomidi, CM_EXTENT_MIDI,
					"C-Media PCI MIDI", NULL) < 0) {
			snd_printk("cmipci: MIDI port is busy\n");
			scard->enable_midi = 0;
		}
	}

	if (scard->enable_synth) {
		if (snd_register_ioport(card, scard->iosynth, CM_EXTENT_SYNTH,
					"C-Media PCI FM", NULL) < 0) {
			snd_printk("cmipci: synth port is busy\n");
			scard->enable_synth = 0;
		}
	}

	if ((err = snd_cmipci_create(card, scard, &cm)) < 0)
		goto __nodev;
	scard->cmipci = cm;
	if ((err = snd_cmipci_mixer_new(cm, 0, 0, &mixer)) < 0)
		goto __nodev;
	if ((err = snd_cmipci_pcm_new(cm, 0, &pcm)) < 0)
		goto __nodev;
	if (scard->enable_midi) {
		err = snd_mpu401_uart_new(card, 0, MPU401_HW_SB,
					  cm->iomidi,
					  cm->irqptr->irq, &rmidi);
		if (err < 0)
			goto __nodev;
	}
	strcpy(card->abbreviation, "C-Media PCI");
	strcpy(card->shortname, "C-Media PCI");
	sprintf(card->longname, "%s %s at 0x%lx, irq %li",
		card->shortname,
		card->type == SND_CARD_TYPE_CMI8738 ? "CM8738" : "CM8338",
		pci_resource_start(cm->pci, 0),
		cm->irqptr->irq);

	snd_printd("%s is detected\n", card->longname);

	if ((err = snd_card_register(card)) >= 0) {
		scard->card = card;
		scard->mixer = mixer;
		scard->pcm = pcm;
		scard->rmidi = cm->rmidi = rmidi;
		PCI_SET_DRIVER_DATA(pci, card);
		dev++;
		return 0;
	}

__nodev:
	snd_card_free(card);
	return err;

}

static void __exit snd_cmipci_remove(struct pci_dev *pci)
{
	snd_card_free(PCI_GET_DRIVER_DATA(pci));
	PCI_SET_DRIVER_DATA(pci, NULL);
}


static struct pci_driver driver = {
	name: "C-Media PCI",
	id_table: snd_cmipci_ids,
	probe: snd_cmipci_probe,
	remove: snd_cmipci_remove,
};
	
static int __init alsa_card_cmipci_init(void)
{
	int err;

	if ((err = pci_module_init(&driver)) < 0) {
#ifdef MODULE
		snd_printk("C-Media PCI soundcard not found or device busy\n");
#endif
		return err;
	}
	return 0;
}

static void __exit alsa_card_cmipci_exit(void)
{
	pci_unregister_driver(&driver);
}

module_init(alsa_card_cmipci_init)
module_exit(alsa_card_cmipci_exit)
