/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *  Routines for control of ICE1712 chips
 *
 *  BUGS:
 *    --
 *
 *  TODO:
 *    --
 *
 *   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/control.h"
#include "../../include/info.h"
#include "../../include/ice1712.h"
#include "../../include/mpu401.h"

static int snd_ice1712_build_pro_mixer(ice1712_t *ice, int pcm_pro_dev);

/*
 *  Basic I/O
 */
 
static inline void snd_ice1712_write(ice1712_t * ice, unsigned char addr, unsigned char data)
{
	outb(addr, ICEREG(ice, INDEX));
	outb(data, ICEREG(ice, DATA));
}

static inline unsigned char snd_ice1712_read(ice1712_t * ice, unsigned char addr)
{
	outb(addr, ICEREG(ice, INDEX));
	return inb(ICEREG(ice, DATA));
}

static void snd_ice1712_ac97_write(void *private_data,
				   unsigned short reg,
				   unsigned short val)
{
	ice1712_t *ice = (ice1712_t *)private_data;
	int tm;
	unsigned char old_cmd = 0;

	for (tm = 0; tm < 0x10000; tm++) {
		old_cmd = ICEREG(ice, AC97_CMD);
		if (old_cmd & (ICE1712_AC97_WRITE | ICE1712_AC97_READ))
			continue;
		if (!(old_cmd & ICE1712_AC97_READY))
			continue;
		break;
	}
	outb(reg, ICEREG(ice, AC97_INDEX));
	outw(val, ICEREG(ice, AC97_DATA));
	old_cmd &= ~(ICE1712_AC97_PBK_VSR | ICE1712_AC97_CAP_VSR);
	outb(old_cmd | ICE1712_AC97_WRITE, ICEREG(ice, AC97_CMD));
	for (tm = 0; tm < 0x10000; tm++)
		if ((inb(ICEREG(ice, AC97_CMD)) & ICE1712_AC97_WRITE) == 0)
			break;
}

static unsigned short snd_ice1712_ac97_read(void *private_data,
					     unsigned short reg)
{
	ice1712_t *ice = (ice1712_t *)private_data;
	int tm;
	unsigned char old_cmd = 0;

	for (tm = 0; tm < 0x10000; tm++) {
		old_cmd = ICEREG(ice, AC97_CMD);
		if (old_cmd & (ICE1712_AC97_WRITE | ICE1712_AC97_READ))
			continue;
		if (!(old_cmd & ICE1712_AC97_READY))
			continue;
		break;
	}
	outb(reg, ICEREG(ice, AC97_INDEX));
	outb(old_cmd | ICE1712_AC97_READ, ICEREG(ice, AC97_CMD));
	for (tm = 0; tm < 0x10000; tm++)
		if ((inb(ICEREG(ice, AC97_CMD)) & ICE1712_AC97_READ) == 0)
			break;
	if (tm >= 0x10000)		/* timeout */
		return 0;
	return inw(ICEREG(ice, AC97_DATA));
}

#define CODEC_A 0x40
#define CODEC_B 0x80

static void snd_ice1712_ak4524_write(ice1712_t *ice, unsigned char codecs_mask,
				     unsigned char addr, unsigned char data)
{
	unsigned char tmp;
	int idx;
	int addrdata;
	down(&ice->gpio_mutex);
	tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
	tmp &= ~codecs_mask;
	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
	udelay(150);
	addr &= 0x07;
	addrdata = 0xa000 | (addr << 8) | data;
	for (idx = 15; idx >= 0; idx--) {
		tmp &= ~0x30;
		if (addrdata & (1 << idx))
			tmp |= 0x10;
		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
		udelay(200);
		tmp |= 0x20;
		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
		/* We give the control to the scheduler every 0.6 ms */
		if (idx % 2 == 0) {
			set_current_state(TASK_UNINTERRUPTIBLE);
			schedule_timeout(1);
		} else
			udelay(200);
	}
	tmp |= codecs_mask;
	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
	udelay(150);
	up(&ice->gpio_mutex);
}


static void snd_ice1712_delta_set_spdif_out(ice1712_t *ice, snd_pcm_subchn_t *subchn)
{
	unsigned char val, tmp;
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int idx;

	if (!runtime->digital.dig_valid) {
		val = ice->spdif_defaults;
		if (((val ^ 1) & 1) == SND_PCM_DIG0_PROFESSIONAL) {
			/* CHECKME: I'm not sure about the bit order in val here */
			val &= ~0x18;
			switch (runtime->format.rate) {
			case 32000:	val |= 0x00; break;
			case 44100:	val |= 0x10; break;
			case 48000:	val |= 0x08; break;
			default:	val |= 0x18; break;	/* not identified */
			}
		} else {
			/* CHECKME: I'm not sure about the bit order in val here */
			val &= ~0x06;
			switch (runtime->format.rate) {
			case 32000:	val |= 0x04; break;
			case 48000:	val |= 0x02; break;
			default:	val |= 0x00; break;	/* 44100Hz & all others */
			}
		}
	} else {
		tmp = runtime->digital.dig_status[0];
		val = tmp ^ SND_PCM_DIG0_PROFESSIONAL;
		if (tmp & SND_PCM_DIG0_PROFESSIONAL) {	/* professional */
			if (!(tmp & SND_PCM_DIG0_NONAUDIO))
				val |= 0x02;
			/* CHECKME: I'm not sure about the bit order in val here */
			switch (tmp & SND_PCM_DIG0_PRO_FS) {
			case SND_PCM_DIG0_PRO_FS_32000:	val |= 0x00; break;
			case SND_PCM_DIG0_PRO_FS_44100:	val |= 0x10; break;	/* 44.1kHz */
			case SND_PCM_DIG0_PRO_FS_48000:	val |= 0x08; break;	/* 48kHz */
			default:
			case SND_PCM_DIG0_PRO_FS_NOTID: val |= 0x18; break;
			}
			switch (tmp & SND_PCM_DIG0_PRO_EMPHASIS) {
			default:
			case SND_PCM_DIG0_PRO_EMPHASIS_NOTID: val |= 0x60; break;
			case SND_PCM_DIG0_PRO_EMPHASIS_NONE: val |= 0x20; break;
			case SND_PCM_DIG0_PRO_EMPHASIS_5015: val |= 0x40; break;
			case SND_PCM_DIG0_PRO_EMPHASIS_CCITT: val |= 0x00; break;
			}
			switch (runtime->digital.dig_status[1] & SND_PCM_DIG1_PRO_MODE) {
			case SND_PCM_DIG1_PRO_MODE_TWO:
			case SND_PCM_DIG1_PRO_MODE_STEREOPHONIC: val |= 0x00; break;
			default: val |= 0x80; break;
			}
		} else {	/* consumer */			
			if (!(tmp & SND_PCM_DIG0_CON_NOT_COPYRIGHT))
				val |= 0x08;	/* copyright */
			switch (tmp & SND_PCM_DIG0_CON_EMPHASIS) {
			default:
			case SND_PCM_DIG0_CON_EMPHASIS_NONE: val |= 0x10; break;
			case SND_PCM_DIG0_CON_EMPHASIS_5015: val |= 0x00; break;
			}
			switch (runtime->digital.dig_status[3] & SND_PCM_DIG3_CON_FS) {
			default:
			case SND_PCM_DIG3_CON_FS_44100: val |= 0x00; break;
			case SND_PCM_DIG3_CON_FS_48000: val |= 0x02; break;
			case SND_PCM_DIG3_CON_FS_32000: val |= 0x08; break;
			}
			tmp = runtime->digital.dig_status[1];
			if ((tmp & SND_PCM_DIG1_CON_LASEROPT_MASK) == SND_PCM_DIG1_CON_LASEROPT_ID) {
				val |= 0x40;	/* CD optical */
			} else if ((tmp & SND_PCM_DIG1_CON_MAGNETIC_MASK) == SND_PCM_DIG1_CON_MAGNETIC_ID) {
				val |= 0x00;	/* DAT */
			} else if ((tmp & SND_PCM_DIG1_CON_DIGDIGCONV_MASK) == SND_PCM_DIG1_CON_DIGDIGCONV_ID) {
				val |= 0x20;	/* PCM encoder/decoder */
			} else if ((tmp & SND_PCM_DIG1_CON_CATEGORY) == SND_PCM_DIG1_CON_GENERAL) {
				val |= 0x60;	/* general category */
			} else {
				val |= 0x40;	/* defaults CD optical */
			}
			if (!(tmp & SND_PCM_DIG1_CON_ORIGINAL))	/* CHECKME */
				val |= 0x80;
		}
	}
	/* send byte to transmitter */
	tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
	for (idx = 7; idx >= 0; idx--) {
		tmp &= ~0x04;
		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
		udelay(100);
		if (val & (1 << idx))
			tmp |= 0x08;
		else
			tmp &= ~0x08;
		tmp |= 0x04;
		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
		udelay(100);
	}
	tmp &= ~0x04;
	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
}

static void snd_ice1712_set_pro_rate(ice1712_t *ice, snd_pcm_subchn_t *subchn)
{
	unsigned int rate;
	unsigned char val, tmp;

	if (inb(ICEMT(ice, PLAYBACK_CONTROL)) & (ICE1712_CAPTURE_START_SHADOW|
						 ICE1712_PLAYBACK_PAUSE|
						 ICE1712_PLAYBACK_START))
		return;
	if (inb(ICEMT(ice, RATE)) & ICE1712_SPDIF_MASTER)
		return;
	rate = subchn->runtime->format.rate;
	if (rate <= 8000) val = 6; else
	if (rate <= 9600) val = 3; else
	if (rate <= 11025) val = 10; else
	if (rate <= 12000) val = 2; else
	if (rate <= 16000) val = 5; else
	if (rate <= 22050) val = 9; else
	if (rate <= 24000) val = 1; else
	if (rate <= 32000) val = 4; else
	if (rate <= 44100) val = 8; else
	if (rate <= 48000) val = 0; else
	if (rate <= 64000) val = 15; else
	if (rate <= 88200) val = 11; else
			   val = 7;	/* 96000Hz */
	outb(val, ICEMT(ice, RATE));
	switch (ice->eeprom.subvendor) {
	case ICE1712_SUBDEVICE_DELTA1010:
	case ICE1712_SUBDEVICE_DELTADIO2496:
	case ICE1712_SUBDEVICE_DELTA66:
	case ICE1712_SUBDEVICE_DELTA44:
		tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
		if (val == 15 || val == 11 || val == 7) {
			tmp |= 0x01;
		} else {
			tmp &= ~0x01;
		}
		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
		break;
	}
}

static void snd_ice1712_change_rate(ice1712_t *ice, snd_pcm_subchn_t *subchn)
{
	unsigned int rate;
	unsigned char val;

	if ((inb(ICEMT(ice, PLAYBACK_CONTROL)) & (ICE1712_CAPTURE_START_SHADOW|
						  ICE1712_PLAYBACK_PAUSE|
						  ICE1712_PLAYBACK_START)) == 0)
		return;
	if ((val = inb(ICEMT(ice, RATE))) & ICE1712_SPDIF_MASTER)
		return;
	rate = subchn->runtime->format.rate;
	switch (val & 15) {
	case 0:	rate = 48000; break;
	case 1: rate = 24000; break;
	case 2: rate = 12000; break;
	case 3: rate = 9600; break;
	case 4: rate = 32000; break;
	case 5: rate = 16000; break;
	case 6: rate = 8000; break;
	case 7: rate = 96000; break;
	case 8: rate = 44100; break;
	case 9: rate = 22050; break;
	case 10: rate = 11025; break;
	case 15: rate = 64000; break;
	}
	subchn->runtime->format.rate = rate;
}

/*
 *  Interrupt handler
 */

void snd_ice1712_interrupt(ice1712_t *ice)
{
	unsigned char status;

	while (1) {
		status = inb(ICEREG(ice, IRQSTAT));
		if (status == 0)
			break;
		if (status & ICE1712_IRQ_MPU1) {
			if (ice->rmidi[0])
				snd_mpu401_uart_interrupt(ice->rmidi[0]);
			outb(ICE1712_IRQ_MPU1, ICEREG(ice, IRQSTAT));
			status &= ~ICE1712_IRQ_MPU1;
		}
		if (status & ICE1712_IRQ_TIMER)
			outb(ICE1712_IRQ_TIMER, ICEREG(ice, IRQSTAT));
		if (status & ICE1712_IRQ_MPU2) {
			if (ice->rmidi[1])
				snd_mpu401_uart_interrupt(ice->rmidi[1]);
			outb(ICE1712_IRQ_MPU2, ICEREG(ice, IRQSTAT));
			status &= ~ICE1712_IRQ_MPU2;
		}
		if (status & ICE1712_IRQ_PROPCM) {
			unsigned char mtstat = inb(ICEMT(ice, IRQ));
			if (mtstat & ICE1712_MULTI_PBKSTATUS) {
				if (ice->playback_pro_subchn)
					snd_pcm_transfer_done(ice->playback_pro_subchn);
				outb(ICE1712_MULTI_PBKSTATUS, ICEMT(ice, IRQ));
			}
			if (mtstat & ICE1712_MULTI_CAPSTATUS) {
				if (ice->capture_pro_subchn)
					snd_pcm_transfer_done(ice->capture_pro_subchn);
				outb(ICE1712_MULTI_CAPSTATUS, ICEMT(ice, IRQ));
			}
		}
		if (status & ICE1712_IRQ_FM)
			outb(ICE1712_IRQ_FM, ICEREG(ice, IRQSTAT));
		if (status & ICE1712_IRQ_PBKDS)
			outb(ICE1712_IRQ_PBKDS, ICEREG(ice, IRQSTAT));
		if (status & ICE1712_IRQ_CONCAP)
			outb(ICE1712_IRQ_CONCAP, ICEREG(ice, IRQSTAT));
		if (status & ICE1712_IRQ_CONPBK)
			outb(ICE1712_IRQ_CONPBK, ICEREG(ice, IRQSTAT));
	}
}

/*
 *  PCM part - consumer I/O
 */

static int snd_ice1712_playback_ioctl(void *private_data,
				      snd_pcm_subchn_t * subchn,
				      unsigned int cmd,
				      unsigned long *arg)
{
	int result;
	// ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);

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

static int snd_ice1712_capture_ioctl(void *private_data,
				     snd_pcm_subchn_t * subchn,
				     unsigned int cmd,
				     unsigned long *arg)
{
	int result;
	// ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);

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

static int snd_ice1712_playback_trigger(void *private_data,
					snd_pcm_subchn_t * pcm,
					int cmd)
{
	// ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);
	return -EIO;
}

static int snd_ice1712_capture_trigger(void *private_data,
				       snd_pcm_subchn_t * subchn,
				       int cmd)
{
	// ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);
	return -EIO;
}

static int snd_ice1712_playback_prepare(void *private_data,
					snd_pcm_subchn_t * subchn)
{
	// unsigned long flags;
	// ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);

	// spin_lock_irqsave(&ice1712->reg_lock, flags);
	// spin_unlock_irqrestore(&ice1712->reg_lock, flags);
	return -EIO;
}

static int snd_ice1712_capture_prepare(void *private_data,
				       snd_pcm_subchn_t * subchn)
{
	// unsigned long flags;
	// ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);

	// spin_lock_irqsave(&ice1712->reg_lock, flags);
	// spin_unlock_irqrestore(&ice1712->reg_lock, flags);
	return -EIO;
}

static unsigned int snd_ice1712_playback_pointer(void *private_data,
						 snd_pcm_subchn_t * subchn)
{
	// unsigned long flags;
	// ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);

	// spin_lock_irqsave(&ice1712->reg_lock, flags);
	// spin_unlock_irqrestore(&ice1712->reg_lock, flags);
	return 0;
}

static unsigned int snd_ice1712_capture_pointer(void *private_data,
						snd_pcm_subchn_t * subchn)
{
	// unsigned long flags;
	// ice1712_t *ice1712 = snd_magic_cast(ice1712_t, private_data, -ENXIO);

	// spin_lock_irqsave(&ice1712->reg_lock, flags);
	// spin_unlock_irqrestore(&ice1712->reg_lock, flags);
	return 0;
}

static snd_pcm_hardware_t snd_ice1712_playback =
{
	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 |
	SND_PCM_CHNINFO_PAUSE,	/* flags */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* hardware formats */
	SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_48000,
	4000,			/* min. rate */
	48000,			/* max. rate */
	1,			/* min. voices */
	2,			/* max. voices */
	64,			/* min. fragment size */
	(128*1024),		/* max. fragment size */
	31,			/* fragment align */
	0,			/* FIFO size (unknown) */
	4,			/* transfer block size */
	snd_ice1712_playback_ioctl,
	snd_ice1712_playback_prepare,
	snd_ice1712_playback_trigger,
	snd_ice1712_playback_pointer
};

static snd_pcm_hardware_t snd_ice1712_capture =
{
	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,	/* flags */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* hardware formats */
	SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_48000,
	4000,			/* min. rate */
	48000,			/* max. rate */
	1,			/* min. voices */
	2,			/* max. voices */
	64,			/* min. fragment size */
	(128*1024),		/* max. fragment size */
	31,			/* fragment align */
	0,			/* FIFO size (unknown) */
	4,			/* transfer block size */
	snd_ice1712_capture_ioctl,
	snd_ice1712_capture_prepare,
	snd_ice1712_capture_trigger,
	snd_ice1712_capture_pointer
};

static int snd_ice1712_playback_open(void *private_data,
			             snd_pcm_subchn_t * subchn)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);
	int err;

	if ((err = snd_pcm_dma_alloc(subchn, ice->dma_conp,
				     "ICE1712 - consumer DAC")) < 0)
		return err;
	ice->playback_con_subchn = subchn;
	subchn->runtime->hw = &snd_ice1712_playback;
	snd_pcm_set_sync(subchn);
	snd_pcm_set_mixer(subchn, ice->mixer->device, ice->ac97->me_playback);
	return 0;
}

static int snd_ice1712_capture_open(void *private_data,
				    snd_pcm_subchn_t * subchn)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);
	int err;

	if ((err = snd_pcm_dma_alloc(subchn, ice->dma_conc,
				     "ICE1712 - consumer ADC")) < 0)
		return err;
	ice->capture_con_subchn = subchn;
	subchn->runtime->hw = &snd_ice1712_capture;	
	snd_pcm_set_sync(subchn);
	snd_pcm_set_mixer(subchn, ice->mixer->device, ice->ac97->me_capture);
	return 0;
}

static int snd_ice1712_playback_close(void *private_data,
				      snd_pcm_subchn_t * subchn)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);

	ice->playback_con_subchn = NULL;
	snd_pcm_dma_free(subchn);
	return 0;
}

static int snd_ice1712_capture_close(void *private_data,
				     snd_pcm_subchn_t * subchn)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);

	ice->capture_con_subchn = NULL;
	snd_pcm_dma_free(subchn);
	return 0;
}

static void snd_ice1712_pcm_free(void *private_data)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, );
	ice->pcm = NULL;
}

int snd_ice1712_pcm(ice1712_t * ice, int device, snd_pcm_t ** rpcm)
{
	snd_pcm_t *pcm;
	int err;

	*rpcm = NULL;
	err = snd_pcm_new(ice->card, "ICE1712 consumer", device, 1, 1, &pcm);
	if (err < 0)
		return err;

	snd_printk("Consumer PCM I/O is not supported at this time!!!\n");

	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].private_data = ice;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].open = snd_ice1712_playback_open;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].close = snd_ice1712_playback_close;

	pcm->chn[SND_PCM_CHANNEL_CAPTURE].private_data = ice;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].open = snd_ice1712_capture_open;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].close = snd_ice1712_capture_close;

	pcm->private_data = ice;
	pcm->private_free = snd_ice1712_pcm_free;
	pcm->info_flags = SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_CAPTURE |
			  SND_PCM_INFO_DUPLEX;
	strcpy(pcm->name, "ICE1712 consumer");
	*rpcm = ice->pcm = pcm;
	return 0;
}

/*
 *  PCM code - professional part (multitrack)
 */

static int snd_ice1712_playback_pro_ioctl(void *private_data,
					  snd_pcm_subchn_t * subchn,
					  unsigned int cmd,
					  unsigned long *arg)
{
	int result;
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);

	result = snd_pcm_lib_ioctl(private_data, subchn, cmd, arg);
	if (result < 0)
		return result;
	if (cmd == SND_PCM_IOCTL1_PARAMS) {
		snd_ice1712_change_rate(ice, subchn);
	} else if (cmd == SND_PCM_IOCTL1_SETUP) {
		snd_pcm_channel_setup_t *setup = (snd_pcm_channel_setup_t *)arg;
		setup->msbits_per_sample = 24;
	}
	return 0;
}

static int snd_ice1712_capture_pro_ioctl(void *private_data,
					 snd_pcm_subchn_t * subchn,
					 unsigned int cmd,
					 unsigned long *arg)
{
	int result;
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);

	result = snd_pcm_lib_ioctl(private_data, subchn, cmd, arg);
	if (result < 0)
		return result;
	if (cmd == SND_PCM_IOCTL1_PARAMS) {
		snd_ice1712_change_rate(ice, subchn);
	} else if (cmd == SND_PCM_IOCTL1_SETUP) {
		snd_pcm_channel_setup_t *setup = (snd_pcm_channel_setup_t *)arg;
		setup->msbits_per_sample = 24;
	}
	return 0;
}

static int snd_ice1712_playback_pro_trigger(void *private_data,
					    snd_pcm_subchn_t * subchn,
					    int cmd)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);
	unsigned int what = ICE1712_PLAYBACK_START, old;

	spin_lock_irqsave(&ice->reg_lock, flags);
	old = inl(ICEMT(ice, PLAYBACK_CONTROL));
	if (cmd == SND_PCM_TRIGGER_SYNC_GO) {
		cmd = SND_PCM_TRIGGER_GO;
		if (ice->capture_pro_subchn && !memcmp(&subchn->runtime->sync_group, &ice->capture_pro_subchn->runtime->sync_group, sizeof(snd_pcm_sync_t)))
			what |= ICE1712_CAPTURE_START_SHADOW;
	}
	if (cmd == SND_PCM_TRIGGER_GO) {
		old |= what;
	} else if (cmd == SND_PCM_TRIGGER_STOP) {
		old &= ~(what | ICE1712_PLAYBACK_PAUSE);
	} else if (cmd == SND_PCM_TRIGGER_PAUSE_PUSH) {
		old |= ICE1712_PLAYBACK_PAUSE;
	} else if (cmd == SND_PCM_TRIGGER_PAUSE_RELEASE) {
		old &= ~ICE1712_PLAYBACK_PAUSE;
	} else {
		spin_unlock_irqrestore(&ice->reg_lock, flags);
		return -EINVAL;
	}
	outl(old, ICEMT(ice, PLAYBACK_CONTROL));
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return 0;
}

static int snd_ice1712_capture_pro_trigger(void *private_data,
					   snd_pcm_subchn_t * subchn,
					   int cmd)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);
	unsigned int what = ICE1712_CAPTURE_START_SHADOW, old;

	spin_lock_irqsave(&ice->reg_lock, flags);
	old = inl(ICEMT(ice, PLAYBACK_CONTROL));
	if (cmd == SND_PCM_TRIGGER_SYNC_GO) {
		cmd = SND_PCM_TRIGGER_GO;
		if (ice->playback_pro_subchn && !memcmp(&subchn->runtime->sync_group, &ice->playback_pro_subchn->runtime->sync_group, sizeof(snd_pcm_sync_t)))
			what |= ICE1712_PLAYBACK_START;
	}
	if (cmd == SND_PCM_TRIGGER_GO) {
		old |= what;
	} else if (cmd == SND_PCM_TRIGGER_STOP) {
		old &= ~what;
	} else {
		spin_unlock_irqrestore(&ice->reg_lock, flags);
		return -EINVAL;
	}
	outl(old, ICEMT(ice, PLAYBACK_CONTROL));
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return 0;
}

static int snd_ice1712_playback_pro_prepare(void *private_data,
					    snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);

	ice->playback_pro_size = snd_pcm_lib_transfer_size(subchn);
	spin_lock_irqsave(&ice->reg_lock, flags);
	snd_ice1712_set_pro_rate(ice, subchn);
	switch (ice->eeprom.subvendor) {
	case ICE1712_SUBDEVICE_DELTA1010:
	case ICE1712_SUBDEVICE_DELTADIO2496:
	case ICE1712_SUBDEVICE_DELTA66:
		snd_ice1712_delta_set_spdif_out(ice, subchn);
		break;
	}
	outl(virt_to_bus(subchn->runtime->dma_area->buf), ICEMT(ice, PLAYBACK_ADDR));
	outw((ice->playback_pro_size >> 2) - 1, ICEMT(ice, PLAYBACK_SIZE));
	outw((snd_pcm_lib_transfer_fragment(subchn) >> 2) - 1, ICEMT(ice, PLAYBACK_COUNT));
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return 0;
}

static int snd_ice1712_capture_pro_prepare(void *private_data,
					   snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);

	ice->capture_pro_size = snd_pcm_lib_transfer_size(subchn);
	spin_lock_irqsave(&ice->reg_lock, flags);
	snd_ice1712_set_pro_rate(ice, subchn);
	outl(virt_to_bus(subchn->runtime->dma_area->buf), ICEMT(ice, CAPTURE_ADDR));
	outw((ice->capture_pro_size >> 2) - 1, ICEMT(ice, CAPTURE_SIZE));
	outw((snd_pcm_lib_transfer_fragment(subchn) >> 2) - 1, ICEMT(ice, CAPTURE_COUNT));
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return 0;
}

static unsigned int snd_ice1712_playback_pro_pointer(void *private_data,
						     snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);
	unsigned int result;

	spin_lock_irqsave(&ice->reg_lock, flags);
	result = ice->playback_pro_size - (inw(ICEMT(ice, PLAYBACK_SIZE)) << 2);
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return result;
}

static unsigned int snd_ice1712_capture_pro_pointer(void *private_data,
						    snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);
	unsigned int result;

	spin_lock_irqsave(&ice->reg_lock, flags);
	result = ice->capture_pro_size - (inw(ICEMT(ice, CAPTURE_SIZE)) << 2);
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return result;
}

static snd_pcm_hardware_t snd_ice1712_playback_pro =
{
	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 |
	SND_PCM_CHNINFO_PAUSE,	/* flags */
	SND_PCM_FMT_S32_LE,	/* hardware formats */
	SND_PCM_RATE_KNOT | SND_PCM_RATE_8000_96000,
	4000,			/* min. rate */
	96000,			/* max. rate */
	10,			/* min. voices */
	10,			/* max. voices */
	10 * 4 * 2,		/* min. fragment size */
	131040,			/* max. fragment size */
	(10 * 4) - 1,		/* fragment align */
	0,			/* FIFO size (unknown) */
	10 * 4 * 2,		/* transfer block size */
	snd_ice1712_playback_pro_ioctl,
	snd_ice1712_playback_pro_prepare,
	snd_ice1712_playback_pro_trigger,
	snd_ice1712_playback_pro_pointer
};

static snd_pcm_hardware_t snd_ice1712_capture_pro =
{
	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 |
	SND_PCM_CHNINFO_PAUSE,	/* flags */
	SND_PCM_FMT_S32_LE,	/* hardware formats */
	SND_PCM_RATE_KNOT | SND_PCM_RATE_8000_96000,
	4000,			/* min. rate */
	96000,			/* max. rate */
	12,			/* min. voices */
	12,			/* max. voices */
	12 * 4 * 2,		/* min. fragment size */
	131040,			/* max. fragment size */
	(12 * 4) - 1,		/* fragment align */
	0,			/* FIFO size (unknown) */
	12 * 4 * 2,		/* transfer block size */
	snd_ice1712_capture_pro_ioctl,
	snd_ice1712_capture_pro_prepare,
	snd_ice1712_capture_pro_trigger,
	snd_ice1712_capture_pro_pointer
};

static int snd_ice1712_playback_pro_open(void *private_data,
					 snd_pcm_subchn_t * subchn)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);
	int err;

	if ((err = snd_pcm_dma_alloc(subchn, ice->dma_prop,
				     "ICE1712 - professional DAC")) < 0)
		return err;
	ice->playback_pro_subchn = subchn;
	subchn->runtime->hw = &snd_ice1712_playback_pro;
	snd_pcm_set_sync(subchn);
	// snd_pcm_set_mixer(subchn, ice->mixer->device, ice->ac97->me_playback);
	return 0;
}

static int snd_ice1712_capture_pro_open(void *private_data,
					snd_pcm_subchn_t * subchn)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);
	snd_pcm_digital_t *dig_mask;
	int err;

	if ((err = snd_pcm_dma_alloc(subchn, ice->dma_proc,
				     "ICE1712 - professional ADC")) < 0)
		return err;
	ice->capture_pro_subchn = subchn;
	dig_mask = (snd_pcm_digital_t *) snd_kcalloc(sizeof(snd_pcm_digital_t), GFP_KERNEL);
	if (dig_mask == NULL) {
		snd_pcm_dma_free(subchn);
		return -ENOMEM;
	}
	subchn->runtime->dig_mask = dig_mask;
	subchn->runtime->dig_mask_free = _snd_kfree;
	dig_mask->dig_status[0] = 0xff;
	dig_mask->dig_status[1] = 0xff;
	dig_mask->dig_status[3] = 0xff;
	dig_mask->dig_valid = 1;
	subchn->runtime->hw = &snd_ice1712_capture_pro;
	snd_pcm_set_sync(subchn);
	// snd_pcm_set_mixer(subchn, ice->mixer->device, ice->ac97->me_playback);
	return 0;
}

static int snd_ice1712_playback_pro_close(void *private_data,
					  snd_pcm_subchn_t * subchn)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);

	ice->playback_pro_subchn = NULL;
	snd_pcm_dma_free(subchn);
	return 0;
}

static int snd_ice1712_capture_pro_close(void *private_data,
					 snd_pcm_subchn_t * subchn)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, -ENXIO);

	ice->capture_pro_subchn = NULL;
	snd_pcm_dma_free(subchn);
	return 0;
}

static void snd_ice1712_pcm_profi_free(void *private_data)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, );
	ice->pcm_pro = NULL;
}

int snd_ice1712_pcm_profi(ice1712_t * ice, int device, snd_pcm_t ** rpcm)
{
	snd_pcm_t *pcm;
	int err;

	*rpcm = NULL;
	err = snd_pcm_new(ice->card, "ICE1712 multi", device, 1, 1, &pcm);
	if (err < 0)
		return err;

	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].private_data = ice;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].open = snd_ice1712_playback_pro_open;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].close = snd_ice1712_playback_pro_close;

	pcm->chn[SND_PCM_CHANNEL_CAPTURE].private_data = ice;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].open = snd_ice1712_capture_pro_open;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].close = snd_ice1712_capture_pro_close;

	pcm->private_data = ice;
	pcm->private_free = snd_ice1712_pcm_profi_free;
	pcm->info_flags = SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_CAPTURE |
			  SND_PCM_INFO_DUPLEX;
	strcpy(pcm->name, "ICE1712 multi");

	*rpcm = ice->pcm_pro = pcm;
	
	if ((err = snd_ice1712_build_pro_mixer(ice, device)) < 0)
		return err;	
	return 0;
}

/*
 *  Mixer section
 */

static void snd_ice1712_update_volume(ice1712_t *ice, int index)
{
	unsigned int vol = ice->pro_volumes[index];
	unsigned short val = 0;

	val |= vol & 0x8000 ? (96 - (vol & 0x7f)) : 0x7f;
	val |= (vol & 0x80000000 ? (96 - ((vol >> 16) & 0x7f)) : 0x7f) << 8;
	outb(index, ICEMT(ice, MONITOR_INDEX));
	outw(val, ICEMT(ice, MONITOR_VOLUME));
}

static int snd_ice1712_mute_pro_playback(snd_kmixer_element_t *element,
					 int w_flag, unsigned int *bitmap)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned short left, right;
	int change = 0, index = element->index;
	
	spin_lock_irqsave(&ice->reg_lock, flags);
	left = (ice->pro_volumes[index] >> 15) & 1;
	right = (ice->pro_volumes[index] >> 31) & 1;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, left);
		snd_mixer_set_bit(bitmap, 1, right);
	} else {
		change = left != snd_mixer_get_bit(bitmap, 0) ||
		         right != snd_mixer_get_bit(bitmap, 1);
		ice->pro_volumes[index] &= ~0x80008000;
		if (snd_mixer_get_bit(bitmap, 0))
			ice->pro_volumes[index] |= 0x8000;
		if (snd_mixer_get_bit(bitmap, 1))
			ice->pro_volumes[index] |= 0x80000000;
		snd_ice1712_update_volume(ice, index);
	}
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return change;
}

static int snd_ice1712_mute_pro_capture(snd_kmixer_element_t *element,
					int w_flag, unsigned int *bitmap)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned short left, right;
	int change = 0, index = element->index + 10;
	
	spin_lock_irqsave(&ice->reg_lock, flags);
	left = (ice->pro_volumes[index] >> 15) & 1;
	right = (ice->pro_volumes[index] >> 31) & 1;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, left);
		snd_mixer_set_bit(bitmap, 1, right);
	} else {
		change = left != snd_mixer_get_bit(bitmap, 0) ||
		         right != snd_mixer_get_bit(bitmap, 1);
		ice->pro_volumes[index] &= ~0x80008000;
		if (snd_mixer_get_bit(bitmap, 0))
			ice->pro_volumes[index] |= 0x8000;
		if (snd_mixer_get_bit(bitmap, 1))
			ice->pro_volumes[index] |= 0x80000000;
		snd_ice1712_update_volume(ice, index);
	}
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return change;
}

static int snd_ice1712_volume_pro_playback(snd_kmixer_element_t *element,
					   int w_flag, int *volume)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned short left, right;
	int change = 0, index = element->index;
	
	spin_lock_irqsave(&ice->reg_lock, flags);
	left = (ice->pro_volumes[index] >> 0) & 0x7f;
	right = (ice->pro_volumes[index] >> 16) & 0x7f;
	if (!w_flag) {
		volume[0] = left;
		volume[1] = right;
	} else {
		change = left != volume[0] || right != volume[1];
		ice->pro_volumes[index] &= ~0x007f007f;
		ice->pro_volumes[index] |= volume[0] | (volume[1] << 16);
                snd_ice1712_update_volume(ice, index);
	}
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return change;
}

static int snd_ice1712_volume_pro_capture(snd_kmixer_element_t *element,
					   int w_flag, int *volume)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned short left, right;
	int change = 0, index = element->index + 10;
	
	spin_lock_irqsave(&ice->reg_lock, flags);
	left = (ice->pro_volumes[index] >> 0) & 0x7f;
	right = (ice->pro_volumes[index] >> 16) & 0x7f;
	if (!w_flag) {
		volume[0] = left;
		volume[1] = right;
	} else {
		change = left != volume[0] || right != volume[1];
		ice->pro_volumes[index] &= ~0x007f007f;
		ice->pro_volumes[index] |= volume[0] | (volume[1] << 16);
                snd_ice1712_update_volume(ice, index);
	}
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return change;
}

static int snd_ice1712_group_pro_playback(snd_kmixer_group_t * group,
					  snd_kmixer_file_t * file,
					  int w_flag,
					  snd_mixer_group_t * ugroup)
{
	snd_kmixer_element_t *me_vol, *me_sw;
	int voice[2];
	unsigned int bitmap;
	int change = 0;
                        
	me_vol = snd_mixer_element_find(file->mixer, "Multi Playback Volume", group->index, SND_MIXER_ETYPE_VOLUME1);
	snd_debug_check(me_vol == NULL, -EIO);
	me_sw = snd_mixer_element_find(file->mixer, "Multi Playback Switch", group->index, SND_MIXER_ETYPE_SWITCH1);
	snd_debug_check(me_sw == NULL, -EIO);
	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME | SND_MIXER_GRPCAP_MUTE;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		ugroup->mute = 0;
		ugroup->capture = 0;
		snd_ice1712_volume_pro_playback(me_vol, 0, voice);
		ugroup->volume.names.front_left = voice[0];
		ugroup->volume.names.front_right = voice[1];
		ugroup->min = 0;
		ugroup->max = 96;
		snd_ice1712_mute_pro_playback(me_sw, 0, &bitmap);
		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 {
		voice[0] = ugroup->volume.names.front_left % 97;
		voice[1] = ugroup->volume.names.front_right % 97;
		if (snd_ice1712_volume_pro_playback(me_vol, 1, voice) > 0) {
			snd_mixer_element_value_change(file, me_vol, 0);
			change = 1;
		}
		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 (snd_ice1712_mute_pro_playback(me_sw, 1, &bitmap) > 0) {
			snd_mixer_element_value_change(file, me_sw, 0);
			change = 1;
		}
	}
	return change;
}

static int snd_ice1712_group_pro_capture(snd_kmixer_group_t * group,
					 snd_kmixer_file_t * file,
					 int w_flag,
					 snd_mixer_group_t * ugroup)
{
	snd_kmixer_element_t *me_vol, *me_sw;
	int voice[2];
	unsigned int bitmap;
	int change = 0;
                        
	me_vol = snd_mixer_element_find(file->mixer, "Multi Capture Volume", group->index, SND_MIXER_ETYPE_VOLUME1);
	snd_debug_check(me_vol == NULL, -EIO);
	me_sw = snd_mixer_element_find(file->mixer, "Multi Capture Switch", group->index, SND_MIXER_ETYPE_SWITCH1);
	snd_debug_check(me_sw == NULL, -EIO);
	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME | SND_MIXER_GRPCAP_MUTE;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		ugroup->mute = 0;
		ugroup->capture = 0;
		snd_ice1712_volume_pro_capture(me_vol, 0, voice);
		ugroup->volume.names.front_left = voice[0];
		ugroup->volume.names.front_right = voice[1];
		ugroup->min = 0;
		ugroup->max = 96;
		snd_ice1712_mute_pro_capture(me_sw, 0, &bitmap);
		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 {
		voice[0] = ugroup->volume.names.front_left % 97;
		voice[1] = ugroup->volume.names.front_right % 97;
		if (snd_ice1712_volume_pro_capture(me_vol, 1, voice) > 0) {
			snd_mixer_element_value_change(file, me_vol, 0);
			change = 1;
		}
		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 (snd_ice1712_mute_pro_capture(me_sw, 1, &bitmap) > 0) {
			snd_mixer_element_value_change(file, me_sw, 0);
			change = 1;
		}
	}
	return change;
}

#if 0
static int snd_ice1712_ak4524_dac_volume(snd_kmixer_element_t *element,
					 int w_flag, int *volume,
					 int index)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, element->private_data, -ENXIO);
	int change = 0;
	if (!w_flag) {
		volume[0] = ice->ak4524_dac_volume[index];
	} else {
		change = ice->ak4524_dac_volume[index] != volume[0];
		if (change) {
			int codec = index / 2 == 0 ? CODEC_A : CODEC_B;
			int addr = 6 + index % 2;
			ice->ak4524_dac_volume[index] = volume[0];
			snd_ice1712_ak4524_write(ice, codec, addr, volume[0]);
		}
	}
	return change;
}

static int snd_ice1712_ak4524_adc_volume(snd_kmixer_element_t *element,
					 int w_flag, int *volume,
					 int index)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, element->private_data, -ENXIO);
	int change = 0;
	if (!w_flag) {
		volume[0] = ice->ak4524_adc_volume[index];
	} else {
		change = ice->ak4524_adc_volume[index] != volume[0];
		if (change) {
			int codec = index / 2 == 0 ? CODEC_A : CODEC_B;
			int addr = 4 + index % 2;
			ice->ak4524_adc_volume[index] = volume[0];
			snd_ice1712_ak4524_write(ice, codec, addr, volume[0]);
		}
	}
	return change;
}
#endif

static int snd_ice1712_build_pro_mixer(ice1712_t *ice, int pcm_pro_dev)
{
	static struct snd_mixer_element_volume1_range table_range[2] = {
		{0, 96, -14400, 0},
		{0, 96, -14400, 0},
	};
	snd_kmixer_t *mixer = ice->mixer;
	snd_kmixer_element_t *pbk, *cap, *vol, *sw, *io;
	snd_kmixer_group_t *group;
	int idx;

	/* PCM playback */
	for (idx = 0; idx < 10; idx++) {
		if ((group = snd_mixer_lib_group_ctrl(mixer, "Multi Playback", idx, SND_MIXER_OSS_UNKNOWN, snd_ice1712_group_pro_playback, ice)) == NULL)
			goto __error;
		if ((pbk = snd_mixer_lib_pcm1(mixer, "Multi Playback", idx, SND_MIXER_ETYPE_PLAYBACK1, 1, &pcm_pro_dev)) == NULL)
			goto __error;
		if ((vol = snd_mixer_lib_volume1(mixer, "Multi Playback Volume", idx, 2, table_range, snd_ice1712_volume_pro_playback, ice)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, vol) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, pbk, vol) < 0)
			goto __error;
		if ((sw = snd_mixer_lib_sw1(mixer, "Multi Playback Switch", idx, 2, snd_ice1712_mute_pro_playback, ice)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, sw) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, vol, sw) < 0)
			goto __error;
		if ((io = snd_mixer_lib_io_stereo(mixer, "Multi Playback", idx, SND_MIXER_ETYPE_OUTPUT, SND_MIXER_EIO_DIGITAL)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, sw, io) < 0)
			goto __error;
	}

	/* PCM capture */
	for (idx = 0; idx < 10; idx++) {
		if ((group = snd_mixer_lib_group_ctrl(mixer, "Multi Capture", idx, SND_MIXER_OSS_UNKNOWN, snd_ice1712_group_pro_capture, ice)) == NULL)
			goto __error;
		if ((io = snd_mixer_lib_io_stereo(mixer, "Multi Capture", idx, SND_MIXER_ETYPE_INPUT, SND_MIXER_EIO_DIGITAL)) == NULL)
			goto __error;
		if ((vol = snd_mixer_lib_volume1(mixer, "Multi Capture Volume", idx, 2, table_range, snd_ice1712_volume_pro_capture, ice)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, vol) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, io, vol) < 0)
			goto __error;
		if ((sw = snd_mixer_lib_sw1(mixer, "Multi Capture Switch", idx, 10, snd_ice1712_mute_pro_capture, ice)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, sw) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, vol, sw) < 0)
			goto __error;
		if ((cap = snd_mixer_lib_pcm1(mixer, "Multi Capture", idx, SND_MIXER_ETYPE_CAPTURE1, 1, &pcm_pro_dev)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, sw, cap) < 0)
			goto __error;
	}
	
	return 0;

      __error:
      	return -ENOMEM;
}

static void snd_ice1712_mixer_free_ac97(void *private_data)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, );
	ice->mixer = NULL;
}

int snd_ice1712_mixer(ice1712_t * ice, int device, int pcm_count, int *pcm_devs, snd_kmixer_t ** rmixer)
{
	snd_kmixer_t *mixer;
	int err;

	*rmixer = NULL;
	if (!(ice->eeprom.codec & ICE1712_CFG_NO_CON_AC97)) {
		ac97_t ac97;
		memset(&ac97, 0, sizeof(ac97));
		ac97.write = snd_ice1712_ac97_write;
		ac97.read = snd_ice1712_ac97_read;
		ac97.private_data = ice;
		ac97.private_free = snd_ice1712_mixer_free_ac97;
		if ((err = snd_ac97_mixer(ice->card, device, &ac97, pcm_count, pcm_devs, &mixer)) < 0) {
			return err;
		}
		ice->ac97 = snd_magic_cast(ac97_t, mixer->private_data, -ENXIO);
	} else {
		if ((err = snd_mixer_new(ice->card, "ICE1712", device, &mixer)) < 0)
			return err;
		mixer->private_data = ice;
		strcpy(mixer->name, "ICE1712 - multitrack");
	}
	*rmixer = ice->mixer = mixer;
	return 0;
}

/*
 *
 */

static void snd_ice1712_proc_read(snd_info_buffer_t * buffer,
				  void *private_data)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, private_data, );
	int idx;

	snd_iprintf(buffer, "ICE1712\n\n");
	snd_iprintf(buffer, "EEPROM:\n");
	snd_iprintf(buffer, "  Subvendor        : 0x%x\n", ice->eeprom.subvendor);
	snd_iprintf(buffer, "  Size             : %i bytes\n", ice->eeprom.size);
	snd_iprintf(buffer, "  Version          : %i\n", ice->eeprom.version);
	snd_iprintf(buffer, "  Codec            : 0x%x\n", ice->eeprom.codec);
	snd_iprintf(buffer, "  ACLink           : 0x%x\n", ice->eeprom.aclink);
	snd_iprintf(buffer, "  I2S ID           : 0x%x\n", ice->eeprom.i2sID);
	snd_iprintf(buffer, "  S/PDIF           : 0x%x\n", ice->eeprom.spdif);
	snd_iprintf(buffer, "  GPIO mask        : 0x%x\n", ice->eeprom.gpiomask);
	snd_iprintf(buffer, "  GPIO state       : 0x%x\n", ice->eeprom.gpiostate);
	snd_iprintf(buffer, "  GPIO direction   : 0x%x\n", ice->eeprom.gpiodir);
	snd_iprintf(buffer, "  AC'97 main       : 0x%x\n", ice->eeprom.ac97main);
	snd_iprintf(buffer, "  AC'97 pcm        : 0x%x\n", ice->eeprom.ac97pcm);
	snd_iprintf(buffer, "  AC'97 record     : 0x%x\n", ice->eeprom.ac97rec);
	snd_iprintf(buffer, "  AC'97 record src : 0x%x\n", ice->eeprom.ac97recsrc);
	for (idx = 0; idx < 4; idx++)
		snd_iprintf(buffer, "  DAC ID #%i        : 0x%x\n", idx, ice->eeprom.dacID[idx]);
	for (idx = 0; idx < 4; idx++)
		snd_iprintf(buffer, "  ADC ID #%i        : 0x%x\n", idx, ice->eeprom.adcID[idx]);
	for (idx = 0x1c; idx < ice->eeprom.size && idx < 0x1c + sizeof(ice->eeprom.extra); idx++)
		snd_iprintf(buffer, "  Extra #%02i        : 0x%x\n", idx, ice->eeprom.extra[idx - 0x1c]);
}

static void snd_ice1712_proc_init(ice1712_t * ice)
{
	snd_info_entry_t *entry;

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

static void snd_ice1712_proc_done(ice1712_t * ice)
{
	if (ice->proc_entry) {
		snd_info_unregister(ice->proc_entry);
		ice->proc_entry = NULL;
	}
}

/*
 *
 */

static int snd_ice1712_get_eeprom(snd_card_t * card,
				  snd_kswitch_t * kswitch,
				  snd_switch_t * uswitch)
{
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);

	uswitch->type = SND_SW_TYPE_USER_READ_ONLY;
	memcpy(uswitch->value.data8, &ice->eeprom, 32);
	return 0;
}

static int snd_ice1712_set_eeprom(snd_card_t * card,
				  snd_kswitch_t * kswitch,
				  snd_switch_t * uswitch)
{
	return -EINVAL;
}

static snd_kswitch_t snd_ice1712_eeprom =
{
	name:	"ICE1712 EEPROM",
	get:	(snd_get_switch_t *)snd_ice1712_get_eeprom,
	set:	(snd_set_switch_t *)snd_ice1712_set_eeprom,
};

static int snd_ice1712_get_delta1010_wordclock_select(snd_card_t * card,
						      snd_kswitch_t * kswitch,
						      snd_switch_t * uswitch)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);

	uswitch->type = SND_SW_TYPE_BOOLEAN;
	spin_lock_irqsave(&ice->reg_lock, flags);
	uswitch->value.enable = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & 0x10 ? 0 : 1;
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return 0;
}

static int snd_ice1712_set_delta1010_wordclock_select(snd_card_t * card,
						      snd_kswitch_t * kswitch,
						      snd_switch_t * uswitch)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);
	unsigned char val, old_val;

	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	spin_lock_irqsave(&ice->reg_lock, flags);
	val = old_val = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
	if (!uswitch->value.enable)
		val |= 0x10;
	else
		val &= ~0x10;
	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, val);
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return val != old_val;
}

static snd_kswitch_t snd_ice1712_delta1010_wordclock_select =
{
	name:	"Word Clock Sync",
	get:	(snd_get_switch_t *)snd_ice1712_get_delta1010_wordclock_select,
	set:	(snd_set_switch_t *)snd_ice1712_set_delta1010_wordclock_select,
};

static int snd_ice1712_get_delta1010_wordclock_status(snd_card_t * card,
						      snd_kswitch_t * kswitch,
						      snd_switch_t * uswitch)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);

	uswitch->type = SND_SW_TYPE_USER_READ_ONLY;
	spin_lock_irqsave(&ice->reg_lock, flags);
	uswitch->value.enable = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & 0x20 ? 0 : 1;
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return 0;
}

static int snd_ice1712_set_delta1010_wordclock_status(snd_card_t * card,
						      snd_kswitch_t * kswitch,
						      snd_switch_t * uswitch)
{
	return -EINVAL;
}

static snd_kswitch_t snd_ice1712_delta1010_wordclock_status =
{
	name:	"Word Clock Status",
	get:	(snd_get_switch_t *)snd_ice1712_get_delta1010_wordclock_status,
	set:	(snd_set_switch_t *)snd_ice1712_set_delta1010_wordclock_status,
};

static int snd_ice1712_get_deltadio2496_spdif_in_select(snd_card_t * card,
							snd_kswitch_t * kswitch,
							snd_switch_t * uswitch)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);

	uswitch->type = SND_SW_TYPE_BOOLEAN;
	spin_lock_irqsave(&ice->reg_lock, flags);
	uswitch->value.enable = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & 0x10 ? 1 : 0;
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return 0;
}

static int snd_ice1712_set_deltadio2496_spdif_in_select(snd_card_t * card,
							snd_kswitch_t * kswitch,
							snd_switch_t * uswitch)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);
	unsigned char val, old_val;

	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	spin_lock_irqsave(&ice->reg_lock, flags);
	val = old_val = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
	if (uswitch->value.enable)
		val |= 0x10;
	else
		val &= ~0x10;
	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, val);
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return val != old_val;
}

static snd_kswitch_t snd_ice1712_deltadio2496_spdif_in_select =
{
	name:	"S/PDIF Input Optical",
	get:	(snd_get_switch_t *)snd_ice1712_get_deltadio2496_spdif_in_select,
	set:	(snd_set_switch_t *)snd_ice1712_set_deltadio2496_spdif_in_select,
};

static int snd_ice1712_get_delta_spdif_in_status(snd_card_t * card,
						 snd_kswitch_t * kswitch,
						 snd_switch_t * uswitch)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);

	uswitch->type = SND_SW_TYPE_USER_READ_ONLY;
	spin_lock_irqsave(&ice->reg_lock, flags);
	uswitch->value.enable = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & 0x02 ? 1 : 0;
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return 0;
}

static int snd_ice1712_set_delta_spdif_in_status(snd_card_t * card,
						 snd_kswitch_t * kswitch,
						 snd_switch_t * uswitch)
{
	return -EINVAL;
}

static snd_kswitch_t snd_ice1712_delta_spdif_in_status =
{
	name:	"Delta S/PDIF Input Status",
	get:	(snd_get_switch_t *)snd_ice1712_get_delta_spdif_in_status,
	set:	(snd_set_switch_t *)snd_ice1712_set_delta_spdif_in_status,
};

static int snd_ice1712_get_delta_spdif_out_defaults(snd_card_t * card,
						    snd_kswitch_t * kswitch,
						    snd_switch_t * uswitch)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);

	uswitch->type = SND_SW_TYPE_BYTE;
	uswitch->subtype = SND_SW_SUBTYPE_HEXA;
	uswitch->low = 0;
	uswitch->high = 255;
	spin_lock_irqsave(&ice->reg_lock, flags);
	uswitch->value.data8[0] = (unsigned char)ice->spdif_defaults;
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return 0;
}

static int snd_ice1712_set_delta_spdif_out_defaults(snd_card_t * card,
						    snd_kswitch_t * kswitch,
						    snd_switch_t * uswitch)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);
	int change;

	if (uswitch->type != SND_SW_TYPE_BYTE)
		return -EINVAL;
	spin_lock_irqsave(&ice->reg_lock, flags);
	change = ice->spdif_defaults != uswitch->value.data8[0];
	ice->spdif_defaults = uswitch->value.data8[0];
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return change;
}

static snd_kswitch_t snd_ice1712_delta_spdif_out_defaults =
{
	name:	"Delta S/PDIF Output Defaults",
	get:	(snd_get_switch_t *)snd_ice1712_get_delta_spdif_out_defaults,
	set:	(snd_set_switch_t *)snd_ice1712_set_delta_spdif_out_defaults,
};

static int snd_ice1712_get_pro_spdif_master(snd_card_t * card,
					    snd_kswitch_t * kswitch,
					    snd_switch_t * uswitch)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);

	uswitch->type = SND_SW_TYPE_BOOLEAN;
	spin_lock_irqsave(&ice->reg_lock, flags);
	uswitch->value.enable = inb(ICEMT(ice, RATE)) & ICE1712_SPDIF_MASTER ? 1 : 0;
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return 0;
}

static int snd_ice1712_set_pro_spdif_master(snd_card_t * card,
					    snd_kswitch_t * kswitch,
					    snd_switch_t * uswitch)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);
	unsigned char val, old_val;

	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	spin_lock_irqsave(&ice->reg_lock, flags);
	val = old_val = inb(ICEMT(ice, RATE));
	if (uswitch->value.enable)
		val |= ICE1712_SPDIF_MASTER;
	else
		val &= ~ICE1712_SPDIF_MASTER;
	outb(val, ICEMT(ice, RATE));
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return val != old_val;
}

static snd_kswitch_t snd_ice1712_pro_spdif_master =
{
	name:	"Multi Track S/PDIF Master",
	get:	(snd_get_switch_t *)snd_ice1712_get_pro_spdif_master,
	set:	(snd_set_switch_t *)snd_ice1712_set_pro_spdif_master,
};

static int snd_ice1712_get_pro_route(snd_card_t * card,
				     snd_kswitch_t * kswitch,
				     snd_switch_t * uswitch)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);

	uswitch->type = SND_SW_TYPE_USER;
	spin_lock_irqsave(&ice->reg_lock, flags);
	uswitch->value.data32[0] = 0xe345aa01;	/* magic */
	uswitch->value.data16[2] = inw(ICEMT(ice, ROUTE_PSDOUT03));
	uswitch->value.data16[3] = inw(ICEMT(ice, ROUTE_SPDOUT));
	uswitch->value.data32[2] = inl(ICEMT(ice, ROUTE_CAPTURE));
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return 0;
}

static int snd_ice1712_set_pro_route(snd_card_t * card,
				     snd_kswitch_t * kswitch,
				     snd_switch_t * uswitch)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);
	int change = 0;

	if (uswitch->type != SND_SW_TYPE_USER)
		return -EINVAL;
	if (uswitch->value.data32[0] != 0xe345aa01)	/* magic */
		return -EINVAL;
	spin_lock_irqsave(&ice->reg_lock, flags);
	if (inw(ICEMT(ice, ROUTE_PSDOUT03)) != uswitch->value.data16[2])
		change = 1;
	outw(uswitch->value.data16[2], ICEMT(ice, ROUTE_PSDOUT03));
	if (inw(ICEMT(ice, ROUTE_SPDOUT)) != uswitch->value.data16[3])
		change = 1;
	outw(uswitch->value.data16[3], ICEMT(ice, ROUTE_SPDOUT));
	if (inl(ICEMT(ice, ROUTE_CAPTURE)) != uswitch->value.data32[2])
		change = 1;
	outl(uswitch->value.data32[2], ICEMT(ice, ROUTE_CAPTURE));
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return change;
}

static snd_kswitch_t snd_ice1712_mixer_pro_route =
{
	name:	"Multi Track Route",
	get:	(snd_get_switch_t *)snd_ice1712_get_pro_route,
	set:	(snd_set_switch_t *)snd_ice1712_set_pro_route,
};

static int snd_ice1712_get_pro_volume_rate(snd_card_t * card,
					   snd_kswitch_t * kswitch,
					   snd_switch_t * uswitch)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);

	uswitch->type = SND_SW_TYPE_BYTE;
	uswitch->low = 0;
	uswitch->high = 255;
	spin_lock_irqsave(&ice->reg_lock, flags);
	uswitch->value.data8[0] = inb(ICEMT(ice, MONITOR_RATE));
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return 0;
}

static int snd_ice1712_set_pro_volume_rate(snd_card_t * card,
					   snd_kswitch_t * kswitch,
					   snd_switch_t * uswitch)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);
	int change;

	if (uswitch->type != SND_SW_TYPE_BYTE)
		return -EINVAL;
	spin_lock_irqsave(&ice->reg_lock, flags);
	change = inb(ICEMT(ice, MONITOR_RATE)) != uswitch->value.data8[0];
	outb(uswitch->value.data8[0], ICEMT(ice, MONITOR_RATE));
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return change;
}

static snd_kswitch_t snd_ice1712_mixer_pro_volume_rate =
{
	name:	"Multi Track Volume Rate",
	get:	(snd_get_switch_t *)snd_ice1712_get_pro_volume_rate,
	set:	(snd_set_switch_t *)snd_ice1712_set_pro_volume_rate,
};

static int snd_ice1712_get_pro_peak(snd_card_t * card,
				    snd_kswitch_t * kswitch,
				    snd_switch_t * uswitch)
{
	unsigned long flags;
	ice1712_t *ice = snd_magic_cast(ice1712_t, kswitch->private_data, -ENXIO);
	int idx;

	uswitch->type = SND_SW_TYPE_USER_READ_ONLY;
	spin_lock_irqsave(&ice->reg_lock, flags);
	uswitch->value.data32[0] = 0xe345aa02;	/* magic */
	for (idx = 0; idx < 22; idx++) {
		outb(idx, ICEMT(ice, MONITOR_PEAKINDEX));
		uswitch->value.data8[idx + 4] = inb(ICEMT(ice, MONITOR_PEAKDATA));
	}
	spin_unlock_irqrestore(&ice->reg_lock, flags);
	return 0;
}

static int snd_ice1712_set_pro_peak(snd_card_t * card,
				    snd_kswitch_t * kswitch,
				    snd_switch_t * uswitch)
{
	return -EINVAL;
}

static snd_kswitch_t snd_ice1712_mixer_pro_peak =
{
	name:	"Multi Track Peak",
	get:	(snd_get_switch_t *)snd_ice1712_get_pro_peak,
	set:	(snd_set_switch_t *)snd_ice1712_set_pro_peak,
};

/*
 *
 */

static unsigned char snd_ice1712_read_i2c(ice1712_t *ice,
					  unsigned char dev,
					  unsigned char addr)
{
	long t = 0x10000;

	outb(addr, ICEREG(ice, I2C_BYTE_ADDR));
	outb(dev & ~ICE1712_I2C_WRITE, ICEREG(ice, I2C_DEV_ADDR));
	while (t-- > 0 && (inb(ICEREG(ice, I2C_CTRL)) & ICE1712_I2C_BUSY)) ;
	return inb(ICEREG(ice, I2C_DATA));
}

static int snd_ice1712_read_eeprom(ice1712_t *ice)
{
	int dev = 0xa0;		/* EEPROM device address */
	int idx;

	if ((inb(ICEREG(ice, I2C_CTRL)) & ICE1712_I2C_EEPROM) == 0) {
		snd_printk("ICE1712 has not detected EEPROM\n");
		return -EIO;
	}
	ice->eeprom.subvendor = (snd_ice1712_read_i2c(ice, dev, 0x00) << 0) |
				(snd_ice1712_read_i2c(ice, dev, 0x01) << 8) | 
				(snd_ice1712_read_i2c(ice, dev, 0x02) << 16) | 
				(snd_ice1712_read_i2c(ice, dev, 0x03) << 24);
	ice->eeprom.size = snd_ice1712_read_i2c(ice, dev, 0x04);
	if (ice->eeprom.size < 28) {
		snd_printk("ICE1712 has an invalid EEPROM (size = %i)\n", ice->eeprom.size);
		return -EIO;
	}
	ice->eeprom.version = snd_ice1712_read_i2c(ice, dev, 0x05);
	if (ice->eeprom.version != 1) {
		snd_printk("ICE1712 has an EEPROM with invalid version %i\n", ice->eeprom.version);
		return -EIO;
	}
	ice->eeprom.codec = snd_ice1712_read_i2c(ice, dev, 0x06);
	ice->eeprom.aclink = snd_ice1712_read_i2c(ice, dev, 0x07);
	ice->eeprom.i2sID = snd_ice1712_read_i2c(ice, dev, 0x08);
	ice->eeprom.spdif = snd_ice1712_read_i2c(ice, dev, 0x09);
	ice->eeprom.gpiomask = snd_ice1712_read_i2c(ice, dev, 0x0a);
	ice->eeprom.gpiostate = snd_ice1712_read_i2c(ice, dev, 0x0b);
	ice->eeprom.gpiodir = snd_ice1712_read_i2c(ice, dev, 0x0c);
	ice->eeprom.ac97main = (snd_ice1712_read_i2c(ice, dev, 0x0d) << 0) |
			       (snd_ice1712_read_i2c(ice, dev, 0x0e) << 8);
	ice->eeprom.ac97pcm = (snd_ice1712_read_i2c(ice, dev, 0x0f) << 0) |
			      (snd_ice1712_read_i2c(ice, dev, 0x10) << 8);
	ice->eeprom.ac97rec = (snd_ice1712_read_i2c(ice, dev, 0x11) << 0) |
			      (snd_ice1712_read_i2c(ice, dev, 0x12) << 8);
	ice->eeprom.ac97recsrc = snd_ice1712_read_i2c(ice, dev, 0x13) << 0;
	for (idx = 0; idx < 4; idx++) {
		ice->eeprom.dacID[idx] = snd_ice1712_read_i2c(ice, dev, 0x14 + idx);
		ice->eeprom.adcID[idx] = snd_ice1712_read_i2c(ice, dev, 0x18 + idx);
	}
	for (idx = 0x1c; idx < ice->eeprom.size && idx < 0x1c + sizeof(ice->eeprom.extra); idx++)
		ice->eeprom.extra[idx - 0x1c] = snd_ice1712_read_i2c(ice, dev, idx);
	return 0;
}

static void snd_ice1712_ak4524_init(ice1712_t *ice)
{
	static unsigned char inits[8] = { 0x07, 0x03, 0x60, 0x19,
					 0x7f, 0x7f, 0x7f, 0x7f };
	unsigned char reg;
	for (reg = 0; reg < 8; ++reg) {
		snd_ice1712_ak4524_write(ice, CODEC_A | CODEC_B, 
					 reg, inits[reg]);
	}
}

static void snd_ice1712_chip_init(ice1712_t *ice)
{
	outb(ICE1712_RESET | ICE1712_NATIVE, ICEREG(ice, CONTROL));
	udelay(200);
	outb(ICE1712_NATIVE, ICEREG(ice, CONTROL));
	udelay(200);
	pci_write_config_byte(ice->pci, 0x60, ice->eeprom.codec);
	pci_write_config_byte(ice->pci, 0x61, ice->eeprom.aclink);
	pci_write_config_byte(ice->pci, 0x62, ice->eeprom.i2sID);
	pci_write_config_byte(ice->pci, 0x63, ice->eeprom.spdif);
	snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ice->eeprom.gpiomask);
	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, ice->eeprom.gpiodir);
	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, ice->eeprom.gpiostate);
	switch (ice->eeprom.subvendor) {
	case ICE1712_SUBDEVICE_DELTA66:
	case ICE1712_SUBDEVICE_DELTA44:
		snd_ice1712_ak4524_init(ice);
		break;
	}
}

static void snd_ice1712_build_switches(ice1712_t *ice)
{
	snd_control_switch_new(ice->card, &snd_ice1712_eeprom, ice);
	snd_control_switch_new(ice->card, &snd_ice1712_pro_spdif_master, ice);
	snd_control_switch_new(ice->card, &snd_ice1712_mixer_pro_route, ice);
	snd_control_switch_new(ice->card, &snd_ice1712_mixer_pro_volume_rate, ice);
	snd_control_switch_new(ice->card, &snd_ice1712_mixer_pro_peak, ice);
	switch (ice->eeprom.subvendor) {
	case ICE1712_SUBDEVICE_DELTA1010:
		snd_control_switch_new(ice->card, &snd_ice1712_delta1010_wordclock_select, ice);
		snd_control_switch_new(ice->card, &snd_ice1712_delta1010_wordclock_status, ice);
		break;
	case ICE1712_SUBDEVICE_DELTADIO2496:
		snd_control_switch_new(ice->card, &snd_ice1712_deltadio2496_spdif_in_select, ice);
		break;
	}
	switch (ice->eeprom.subvendor) {
	case ICE1712_SUBDEVICE_DELTA1010:
	case ICE1712_SUBDEVICE_DELTADIO2496:
	case ICE1712_SUBDEVICE_DELTA66:
		snd_control_switch_new(ice->card, &snd_ice1712_delta_spdif_in_status, ice);
		snd_control_switch_new(ice->card, &snd_ice1712_delta_spdif_out_defaults, ice);
		break;
	}
}

int snd_ice1712_create(snd_card_t * card,
		       struct pci_dev *pci,
		       snd_dma_t * dma_conp,
		       snd_dma_t * dma_conc,
		       snd_dma_t * dma_prop,
		       snd_dma_t * dma_proc,
		       snd_irq_t * irqptr,
		       ice1712_t ** r_ice1712)
{
	ice1712_t *ice;
	unsigned short cmdw;
	unsigned char cmdb;
	int err;
	static snd_device_ops_t ops = {
		(snd_dev_free_t *)snd_ice1712_free,
		NULL,
		NULL
	};

	*r_ice1712 = NULL;
	ice = snd_magic_kcalloc(ice1712_t, 0, GFP_KERNEL);
	if (ice == NULL)
		return -ENOMEM;
	spin_lock_init(&ice->reg_lock);
	init_MUTEX(&ice->gpio_mutex);
	ice->spdif_defaults = 0x01 |		/* consumer format */
			      0x10 |		/* no emphasis */
			      0x20;		/* PCM encoder/decoder */
	ice->card = card;
	ice->pci = pci;
	ice->dma_conp = dma_conp;
	ice->dma_conc = dma_conc;
	ice->dma_prop = dma_prop;
	ice->dma_proc = dma_proc;
	ice->irqptr = irqptr;
	ice->port = pci_resource_start(pci, 0);
	ice->ddma_port = pci_resource_start(pci, 1);
	ice->dmapath_port = pci_resource_start(pci, 2);
	ice->profi_port = pci_resource_start(pci, 3);
	pci_read_config_word(pci, PCI_COMMAND, &cmdw);
	if ((cmdw & PCI_COMMAND_IO) != PCI_COMMAND_IO) {
		cmdw |= PCI_COMMAND_IO;
		pci_write_config_word(pci, PCI_COMMAND, cmdw);
	}
	pci_set_master(pci);
	pci_read_config_byte(pci, PCI_LATENCY_TIMER, &cmdb);
	if (cmdb < 32)
		cmdb = 32;
	pci_write_config_byte(pci, PCI_LATENCY_TIMER, cmdb);
	snd_ice1712_proc_init(ice);
	synchronize_irq();

	if (snd_ice1712_read_eeprom(ice) < 0) {
		snd_ice1712_free(ice);
		return -EIO;
	}
	snd_ice1712_chip_init(ice);
	snd_ice1712_build_switches(ice);

	/* unmask used interrupts */
	outb((ice->eeprom.codec & ICE1712_CFG_2xMPU401) == 0 ? ICE1712_IRQ_MPU2 : 0 |
	     (ice->eeprom.codec & ICE1712_CFG_NO_CON_AC97) ? ICE1712_IRQ_PBKDS | ICE1712_IRQ_CONCAP | ICE1712_IRQ_CONPBK : 0,
	     ICEREG(ice, IRQMASK));
	outb(0x00, ICEMT(ice, IRQ));

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

	*r_ice1712 = ice;
	return 0;
}

int snd_ice1712_free(ice1712_t * ice)
{
	/* mask all interrupts */
	outb(0xc0, ICEMT(ice, IRQ));
	outb(0xff, ICEREG(ice, IRQMASK));
	/* --- */
	snd_ice1712_proc_done(ice);
	synchronize_irq();
	snd_magic_kfree(ice);
	return 0;
}

EXPORT_SYMBOL(snd_ice1712_interrupt);
EXPORT_SYMBOL(snd_ice1712_create);
EXPORT_SYMBOL(snd_ice1712_pcm);
EXPORT_SYMBOL(snd_ice1712_pcm_profi);
EXPORT_SYMBOL(snd_ice1712_mixer);

/*
 *  INIT part
 */

static int __init alsa_ice1712_init(void)
{
	return 0;
}

static void __exit alsa_ice1712_exit(void)
{
}

module_init(alsa_ice1712_init)
module_exit(alsa_ice1712_exit)
