/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *  Routines for control of YMF724/740/744/754 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/ymfpci.h"

static int snd_ymfpci_free(ymfpci_t * codec);

/*
 *  constants
 */

/*
 *  common I/O routines
 */

static inline u8 snd_ymfpci_readb(ymfpci_t *codec, u32 offset)
{
	return readb(codec->reg_area_virt + offset);
}

static inline void snd_ymfpci_writeb(ymfpci_t *codec, u32 offset, u8 val)
{
	writeb(val, codec->reg_area_virt + offset);
}

static inline u16 snd_ymfpci_readw(ymfpci_t *codec, u32 offset)
{
	return readw(codec->reg_area_virt + offset);
}

static inline void snd_ymfpci_writew(ymfpci_t *codec, u32 offset, u16 val)
{
	writew(val, codec->reg_area_virt + offset);
}

static inline u32 snd_ymfpci_readl(ymfpci_t *codec, u32 offset)
{
	return readl(codec->reg_area_virt + offset);
}

static inline void snd_ymfpci_writel(ymfpci_t *codec, u32 offset, u32 val)
{
	writel(val, codec->reg_area_virt + offset);
}

static int snd_ymfpci_codec_ready(ymfpci_t *codec, int secondary, int sched)
{
	signed long end_time;
	u32 reg = secondary ? YDSXGR_SECSTATUSADR : YDSXGR_PRISTATUSADR;
	
	end_time = jiffies + 3 * (HZ / 4);
	do {
		if ((snd_ymfpci_readw(codec, reg) & 0x8000) == 0)
			return 0;
		if (sched) {
			set_current_state(TASK_UNINTERRUPTIBLE);
			schedule_timeout(1);
		}
	} while (end_time - (signed long)jiffies >= 0);
	snd_printk("snd_ymfpci_codec_ready: codec %i is not ready [0x%x]\n", secondary, snd_ymfpci_readw(codec, reg));
	return -EBUSY;
}

static void snd_ymfpci_codec_write(void *private_data, u16 reg, u16 val)
{
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, );
	u32 cmd;
	
	snd_ymfpci_codec_ready(codec, 0, 0);
	cmd = ((YDSXG_AC97WRITECMD | reg) << 16) | val;
	snd_ymfpci_writel(codec, YDSXGR_AC97CMDDATA, cmd);
}

static u16 snd_ymfpci_codec_read(void *private_data, u16 reg)
{
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, -ENXIO);

	if (snd_ymfpci_codec_ready(codec, 0, 0))
		return ~0;
	snd_ymfpci_writew(codec, YDSXGR_AC97CMDADR, YDSXG_AC97READCMD | reg);
	if (snd_ymfpci_codec_ready(codec, 0, 0))
		return ~0;
	if (codec->device_id == PCI_DEVICE_ID_YAMAHA_744 && codec->rev < 2) {
		int i;
		for (i = 0; i < 600; i++)
			snd_ymfpci_readw(codec, YDSXGR_PRISTATUSDATA);
	}
	return snd_ymfpci_readw(codec, YDSXGR_PRISTATUSDATA);
}

/*
 *  Misc routines
 */

static u32 snd_ymfpci_calc_delta(u32 rate)
{
	switch (rate) {
	case 8000:	return 0x02aaab00;
	case 11025:	return 0x03accd00;
	case 16000:	return 0x05555500;
	case 22050:	return 0x07599a00;
	case 32000:	return 0x0aaaab00;
	case 44100:	return 0x0eb33300;
	default:	return ((rate << 16) / 48000) << 12;
	}
}

static u32 def_rate[8] = {
	100, 2000, 8000, 11025, 16000, 22050, 32000, 48000
};

static u32 snd_ymfpci_calc_lpfK(u32 rate)
{
	u32 i;
	static u32 val[8] = {
		0x00570000, 0x06AA0000, 0x18B20000, 0x20930000,
		0x2B9A0000, 0x35A10000, 0x3EAA0000, 0x40000000
	};
	
	if (rate == 44100)
		return 0x40000000;	/* FIXME: What's the right value? */
	for (i = 0; i < 8; i++)
		if (rate <= def_rate[i])
			return val[i];
	return val[0];
}

static u32 snd_ymfpci_calc_lpfQ(u32 rate)
{
	u32 i;
	static u32 val[8] = {
		0x35280000, 0x34A70000, 0x32020000, 0x31770000,
		0x31390000, 0x31C90000, 0x33D00000, 0x40000000
	};
	
	if (rate == 44100)
		return 0x370A0000;
	for (i = 0; i < 8; i++)
		if (rate <= def_rate[i])
			return val[i];
	return val[0];
}

/*
 *  Hardware start management
 */

static void snd_ymfpci_hw_start(ymfpci_t *codec)
{
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	if (codec->start_count++ > 1)
		goto __end;
	snd_ymfpci_writel(codec, YDSXGR_MODE, 3);
	codec->active_bank = snd_ymfpci_readl(codec, YDSXGR_CTRLSELECT);
      __end:
      	spin_unlock_irqrestore(&codec->reg_lock, flags);
}

static void snd_ymfpci_hw_stop(ymfpci_t *codec)
{
	unsigned long flags;
	long timeout = 1000;

	spin_lock_irqsave(&codec->reg_lock, flags);
	if (--codec->start_count > 0)
		goto __end;
	snd_ymfpci_writel(codec, YDSXGR_MODE, 0);
	while (timeout-- > 0) {
		if ((snd_ymfpci_readl(codec, YDSXGR_STATUS) & 2) == 0)
			break;
	}
      __end:
      	spin_unlock_irqrestore(&codec->reg_lock, flags);
}

/*
 *  Playback voice management
 */

static int voice_alloc(ymfpci_t *codec, ymfpci_voice_type_t type, int pair, ymfpci_voice_t **rvoice)
{
	ymfpci_voice_t *voice, *voice2;
	int idx;
	
	*rvoice = NULL;
	for (idx = 0; idx < 64; idx += pair ? 2 : 1) {
		voice = &codec->voices[idx];
		voice2 = pair ? &codec->voices[idx+1] : NULL;
		if (voice->use || (voice2 && voice2->use))
			continue;
		voice->use = 1;
		if (voice2)
			voice2->use = 1;
		switch (type) {
		case YMFPCI_PCM:
			voice->pcm = 1;
			if (voice2)
				voice2->pcm = 1;
			break;
		case YMFPCI_SYNTH:
			voice->synth = 1;
			break;
		case YMFPCI_MIDI:
			voice->midi = 1;
			break;
		}
		snd_ymfpci_hw_start(codec);
		if (voice2)
			snd_ymfpci_hw_start(codec);
		*rvoice = voice;
		return 0;
	}
	return -ENOMEM;
}

int snd_ymfpci_voice_alloc(ymfpci_t *codec, ymfpci_voice_type_t type, int pair, ymfpci_voice_t **rvoice)
{
	unsigned long flags;
	int result;
	
	snd_debug_check(rvoice == NULL, -EINVAL);
	snd_debug_check(pair && type != YMFPCI_PCM, -EINVAL);
	
	spin_lock_irqsave(&codec->voice_lock, flags);
	for (;;) {
		result = voice_alloc(codec, type, pair, rvoice);
		if (result == 0 || type != YMFPCI_PCM)
			break;
		/* TODO: synth/midi voice deallocation */
		break;
	}
	spin_unlock_irqrestore(&codec->voice_lock, flags);	
	return result;		
}

int snd_ymfpci_voice_free(ymfpci_t *codec, ymfpci_voice_t *pvoice)
{
	unsigned long flags;
	
	snd_debug_check(pvoice == NULL, -EINVAL);
	snd_ymfpci_hw_stop(codec);
	spin_lock_irqsave(&codec->voice_lock, flags);
	pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = 0;
	pvoice->ypcm = NULL;
	pvoice->interrupt = NULL;
	spin_unlock_irqrestore(&codec->voice_lock, flags);
	return 0;
}

/*
 *  PCM part
 */

static void snd_ymfpci_pcm_interrupt(ymfpci_t *codec, ymfpci_voice_t *voice)
{
	ymfpci_pcm_t *ypcm;
	u32 pos, delta;
	
	if ((ypcm = voice->ypcm) == NULL)
		return;
	if (ypcm->substream == NULL)
		return;
	spin_lock(&codec->reg_lock);
	if (ypcm->running) {
		pos = voice->bank[codec->active_bank].start << ypcm->shift_offset;
		if (pos < ypcm->last_pos)
			delta = pos + (ypcm->buffer_size - ypcm->last_pos);
		else
			delta = pos - ypcm->last_pos;
		ypcm->frag_pos += delta;
		ypcm->last_pos = pos;
		while (ypcm->frag_pos >= ypcm->frag_size) {
			ypcm->frag_pos -= ypcm->frag_size;
			// printk("done - active_bank = 0x%x, start = 0x%x\n", codec->active_bank, voice->bank[codec->active_bank].start);
			spin_unlock(&codec->reg_lock);
			snd_pcm_transfer_done(ypcm->substream);
			spin_lock(&codec->reg_lock);
		}
	}
	spin_unlock(&codec->reg_lock);
}

static void snd_ymfpci_pcm_capture_interrupt(snd_pcm_subchn_t *substream)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	ymfpci_pcm_t *ypcm = snd_magic_cast(ymfpci_pcm_t, runtime->private_data, );
	ymfpci_t *codec = ypcm->codec;
	u32 pos, delta;
	
	spin_lock(&codec->reg_lock);
	if (ypcm->running) {
		pos = codec->bank_capture[ypcm->capture_bank_number][codec->active_bank]->start << ypcm->shift_offset;
		if (pos < ypcm->last_pos)
			delta = pos + (ypcm->buffer_size - ypcm->last_pos);
		else
			delta = pos - ypcm->last_pos;
		ypcm->frag_pos += delta;
		ypcm->last_pos = pos;
		while (ypcm->frag_pos >= ypcm->frag_size) {
			ypcm->frag_pos -= ypcm->frag_size;
			// printk("done - active_bank = 0x%x, start = 0x%x\n", codec->active_bank, voice->bank[codec->active_bank].start);
			spin_unlock(&codec->reg_lock);
			snd_pcm_transfer_done(substream);
			spin_lock(&codec->reg_lock);
		}
	}
	spin_unlock(&codec->reg_lock);
}

static int snd_ymfpci_playback_ioctl(void *private_data,
				     snd_pcm_subchn_t * substream,
				     unsigned int cmd,
				     unsigned long *arg)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	int result;

	result = snd_pcm_lib_ioctl(private_data, substream, cmd, arg);
	if (result < 0)
		return result;		
	if (cmd == SND_PCM_IOCTL1_PARAMS) {
		if (runtime->mode == SND_PCM_MODE_BLOCK) {
			u32 tmp;
			tmp = (u32)runtime->format.rate << 16;
			tmp /= 48000;
			tmp >>= 8;
			if (snd_pcm_format_width(runtime->format.format) == 16)
				tmp <<= 1;
			if (runtime->format.voices == 2)
				tmp <<= 1;
			runtime->frags = snd_pcm_lib_transfer_size(substream) / tmp;
			runtime->buf.block.frag_size = tmp;
			snd_pcm_lib_set_buffer_size(substream, runtime->frags * runtime->buf.block.frag_size);
		}
	}
	return result;
}

static int snd_ymfpci_capture_ioctl(void *private_data,
				    snd_pcm_subchn_t * substream,
				    unsigned int cmd,
				    unsigned long *arg)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	int result;

	result = snd_pcm_lib_ioctl(private_data, substream, cmd, arg);
	if (result < 0)
		return result;		
	if (cmd == SND_PCM_IOCTL1_PARAMS) {
		if (runtime->mode == SND_PCM_MODE_BLOCK) {
			u32 tmp;
			tmp = (u32)runtime->format.rate << 16;
			tmp /= 48000;
			tmp >>= 8;
			if (snd_pcm_format_width(runtime->format.format) == 16)
				tmp <<= 1;
			if (runtime->format.voices == 2)
				tmp <<= 1;
			runtime->frags = snd_pcm_lib_transfer_size(substream) / tmp;
			runtime->buf.block.frag_size = tmp;
			snd_pcm_lib_set_buffer_size(substream, runtime->frags * runtime->buf.block.frag_size);
		}
	}
	return result;
}

static int snd_ymfpci_playback_trigger(void *private_data,
				       snd_pcm_subchn_t * substream,
				       int cmd)
{
	unsigned long flags;
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, -ENXIO);
	ymfpci_pcm_t *ypcm = snd_magic_cast(ymfpci_pcm_t, substream->runtime->private_data, -ENXIO);
	int result = 0;

	spin_lock_irqsave(&codec->reg_lock, flags);
	if (ypcm->voices[0] == NULL) {
		result = -EINVAL;
	} if (cmd == SND_PCM_TRIGGER_GO) {
		codec->ctrl_playback[ypcm->voices[0]->number + 1] = virt_to_bus(ypcm->voices[0]->bank);
		if (ypcm->voices[1] != NULL)
			codec->ctrl_playback[ypcm->voices[1]->number + 1] = virt_to_bus(ypcm->voices[1]->bank);
		ypcm->running = 1;
	} else if (cmd == SND_PCM_TRIGGER_STOP) {
		codec->ctrl_playback[ypcm->voices[0]->number + 1] = 0;
		if (ypcm->voices[1] != NULL)
			codec->ctrl_playback[ypcm->voices[1]->number + 1] = 0;
		ypcm->running = 0;
	} else {
		result = -EINVAL;
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return result;
}

static int snd_ymfpci_capture_trigger(void *private_data,	
				      snd_pcm_subchn_t * substream,
				      int cmd)
{
	unsigned long flags;
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, -ENXIO);
	ymfpci_pcm_t *ypcm = snd_magic_cast(ymfpci_pcm_t, substream->runtime->private_data, -ENXIO);
	int result = 0;
	u32 tmp;

	spin_lock_irqsave(&codec->reg_lock, flags);
	if (cmd == SND_PCM_TRIGGER_GO) {
		tmp = snd_ymfpci_readl(codec, YDSXGR_MAPOFREC) | (1 << ypcm->capture_bank_number);
		snd_ymfpci_writel(codec, YDSXGR_MAPOFREC, tmp);
		ypcm->running = 1;
	} else if (cmd == SND_PCM_TRIGGER_STOP) {
		tmp = snd_ymfpci_readl(codec, YDSXGR_MAPOFREC) & ~(1 << ypcm->capture_bank_number);
		snd_ymfpci_writel(codec, YDSXGR_MAPOFREC, tmp);
		ypcm->running = 0;
	} else {
		result = -EINVAL;
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return result;
}

static int snd_ymfpci_pcm_voice_alloc(ymfpci_pcm_t *ypcm, int voices)
{
	int err;

	if (ypcm->voices[1] != NULL && voices < 2) {
		snd_ymfpci_voice_free(ypcm->codec, ypcm->voices[1]);
		ypcm->voices[1] = NULL;
	}
	if (voices == 1 && ypcm->voices[0] != NULL)
		return 0;		/* already allocated */
	if (voices == 2 && ypcm->voices[0] != NULL && ypcm->voices[1] != NULL)
		return 0;		/* already allocated */
	if (voices > 1) {
		if (ypcm->voices[0] != NULL && ypcm->voices[1] == NULL) {
			snd_ymfpci_voice_free(ypcm->codec, ypcm->voices[0]);
			ypcm->voices[0] = NULL;
		}		
	}
	err = snd_ymfpci_voice_alloc(ypcm->codec, YMFPCI_PCM, voices > 1, &ypcm->voices[0]);
	if (err < 0)
		return err;
	ypcm->voices[0]->ypcm = ypcm;
	ypcm->voices[0]->interrupt = snd_ymfpci_pcm_interrupt;
	if (voices > 1) {
		ypcm->voices[1] = &ypcm->codec->voices[ypcm->voices[0]->number + 1];
		ypcm->voices[1]->ypcm = ypcm;
	}
	return 0;
}

static void snd_ymfpci_pcm_init_voice(ymfpci_voice_t *voice, int stereo,
				      int rate, int w_16, unsigned long addr,
				      unsigned int end, int spdif)
{
	u32 format;
	u32 delta = snd_ymfpci_calc_delta(rate);
	u32 lpfQ = snd_ymfpci_calc_lpfQ(rate);
	u32 lpfK = snd_ymfpci_calc_lpfK(rate);
	snd_ymfpci_playback_bank_t *bank;
	unsigned int nbank;

	snd_debug_check(voice == NULL, );
	format = (stereo ? 0x00010000 : 0) | (w_16 ? 0 : 0x80000000);
	if (stereo)
		end >>= 1;
	if (w_16)
		end >>= 1;
	for (nbank = 0; nbank < 2; nbank++) {
		bank = &voice->bank[nbank];
		bank->format = format;
		bank->loop_default = 0;
		bank->base = addr;
		bank->loop_start = 0;
		bank->loop_end = end;
		bank->loop_frac = 0;
		bank->eg_gain_end = 0x40000000;
		bank->lpfQ = lpfQ;
		bank->status = 0;
		bank->num_of_frames = 0;
		bank->loop_count = 0;
		bank->start = 0;
		bank->start_frac = 0;
		bank->delta =
		bank->delta_end = delta;
		bank->lpfK =
		bank->lpfK_end = lpfK;
		bank->eg_gain = 0x40000000;
		bank->lpfD1 =
		bank->lpfD2 = 0;

		bank->left_gain = 
		bank->right_gain =
		bank->left_gain_end =
		bank->right_gain_end =
		bank->eff1_gain =
		bank->eff2_gain =
		bank->eff3_gain =
		bank->eff1_gain_end =
		bank->eff2_gain_end =
		bank->eff3_gain_end = 0;

		if (!stereo) {
			if (!spdif) {
				bank->left_gain = 
				bank->right_gain =
				bank->left_gain_end =
				bank->right_gain_end = 0x40000000;
			} else {
				bank->eff2_gain =
				bank->eff2_gain_end =
				bank->eff3_gain =
				bank->eff3_gain_end = 0x40000000;
			}
		} else {
			if (!spdif) {
				if ((voice->number & 1) == 0) {
					bank->left_gain =
					bank->left_gain_end = 0x40000000;
				} else {
					bank->format |= 1;
					bank->right_gain =
					bank->right_gain_end = 0x40000000;
				}
			} else {
				if ((voice->number & 1) == 0) {
					bank->eff2_gain =
					bank->eff2_gain_end = 0x40000000;
				} else {
					bank->format |= 1;
					bank->eff3_gain =
					bank->eff3_gain_end = 0x40000000;
				}
			}
		}
	}
}

static int snd_ymfpci_playback_prepare(void *private_data,
				       snd_pcm_subchn_t * substream)
{
	// ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = substream->runtime;
	ymfpci_pcm_t *ypcm = snd_magic_cast(ymfpci_pcm_t, runtime->private_data, -ENXIO);
	int err, nvoice;

	ypcm->frag_size = snd_pcm_lib_transfer_fragment(substream);
	ypcm->buffer_size = snd_pcm_lib_transfer_size(substream);
	if ((err = snd_ymfpci_pcm_voice_alloc(ypcm, runtime->format.voices)) < 0)
		return err;
	ypcm->frag_pos = 0;
	ypcm->last_pos = 0;
	ypcm->shift_offset = 0;
	if (runtime->format.voices == 2)
		ypcm->shift_offset++;
	if (snd_pcm_format_width(runtime->format.format) == 16)
		ypcm->shift_offset++;
	for (nvoice = 0; nvoice < runtime->format.voices; nvoice++)
		snd_ymfpci_pcm_init_voice(ypcm->voices[nvoice],
					  runtime->format.voices == 2,
					  runtime->format.rate,
					  snd_pcm_format_width(runtime->format.format) == 16,
					  virt_to_bus(runtime->dma_area->buf),
					  ypcm->buffer_size,
					  ypcm->spdif);
	return 0;
}

static int snd_ymfpci_capture_prepare(void *private_data,
				      snd_pcm_subchn_t * substream)
{
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = substream->runtime;
	ymfpci_pcm_t *ypcm = snd_magic_cast(ymfpci_pcm_t, runtime->private_data, -ENXIO);
	snd_ymfpci_capture_bank_t * bank;
	int nbank;
	u32 rate, format;

	ypcm->frag_size = snd_pcm_lib_transfer_fragment(substream);
	ypcm->buffer_size = snd_pcm_lib_transfer_size(substream);
	ypcm->frag_pos = 0;
	ypcm->last_pos = 0;
	ypcm->shift_offset = 0;
	rate = ((48000 * 4096) / runtime->format.rate) - 1;
	format = 0;
	if (runtime->format.voices == 2)
		format |= 2;
	if (snd_pcm_format_width(runtime->format.format) == 8)
		format |= 1;
	switch (ypcm->capture_bank_number) {
	case 0:
		snd_ymfpci_writel(codec, YDSXGR_RECFORMAT, format);
		snd_ymfpci_writel(codec, YDSXGR_RECSLOTSR, rate);
		break;
	case 1:
		snd_ymfpci_writel(codec, YDSXGR_ADCFORMAT, format);
		snd_ymfpci_writel(codec, YDSXGR_ADCSLOTSR, rate);
		break;
	}
	for (nbank = 0; nbank < 2; nbank++) {
		bank = codec->bank_capture[ypcm->capture_bank_number][nbank];
		bank->base = virt_to_bus(runtime->dma_area->buf);
		bank->loop_end = ypcm->buffer_size;
		bank->start = 0;
		bank->num_of_loops = 0;
	}
	if (runtime->digital.dig_valid)
		/*runtime->digital.type == SND_PCM_DIG_AES_IEC958*/
		snd_ymfpci_writew(codec, YDSXGR_SPDIFOUTSTATUS, runtime->digital.dig_status[0] |
								(runtime->digital.dig_status[1] << 8));
	return 0;
}

static unsigned int snd_ymfpci_playback_pointer(void *private_data,
						snd_pcm_subchn_t * substream)
{
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = substream->runtime;
	ymfpci_pcm_t *ypcm = snd_magic_cast(ymfpci_pcm_t, runtime->private_data, -ENXIO);
	ymfpci_voice_t *voice = ypcm->voices[0];
	unsigned long flags;
	unsigned int result;

	spin_lock_irqsave(&codec->reg_lock, flags);
	if (ypcm->running && voice)
		result = voice->bank[codec->active_bank].start << ypcm->shift_offset;
	else
		result = 0;
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return result;
}

static unsigned int snd_ymfpci_capture_pointer(void *private_data,
					       snd_pcm_subchn_t * substream)
{
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = substream->runtime;
	ymfpci_pcm_t *ypcm = snd_magic_cast(ymfpci_pcm_t, runtime->private_data, -ENXIO);
	unsigned long flags;
	unsigned int result;

	spin_lock_irqsave(&codec->reg_lock, flags);
	if (ypcm->running)
		result = codec->bank_capture[ypcm->capture_bank_number][codec->active_bank]->start << ypcm->shift_offset;
	else
		result = 0;
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return result;
}

void snd_ymfpci_interrupt(ymfpci_t *codec)
{
	u32 status, nvoice, mode;
	ymfpci_voice_t *voice;

	status = snd_ymfpci_readl(codec, YDSXGR_STATUS);
	if (status & 0x80000000) {
		spin_lock(&codec->reg_lock);
		snd_ymfpci_writel(codec, YDSXGR_STATUS, 0x80000000);
		mode = snd_ymfpci_readl(codec, YDSXGR_MODE) | 2;
		snd_ymfpci_writel(codec, YDSXGR_MODE, mode);
		spin_unlock(&codec->reg_lock);
		codec->active_bank = snd_ymfpci_readl(codec, YDSXGR_CTRLSELECT);
		spin_lock(&codec->voice_lock);
		for (nvoice = 0; nvoice < 64; nvoice++) {
			voice = &codec->voices[nvoice];
			if (voice->interrupt)
				voice->interrupt(codec, voice);
		}
		for (nvoice = 0; nvoice < 5; nvoice++) {
			if (codec->capture_substream[nvoice])
				snd_ymfpci_pcm_capture_interrupt(codec->capture_substream[nvoice]);
		}
		spin_unlock(&codec->voice_lock);
	}

	status = snd_ymfpci_readl(codec, YDSXGR_INTFLAG);
	if (status & 1) {
		/* timer handler */
		snd_ymfpci_writel(codec, YDSXGR_INTFLAG, ~0);
	}
}

static snd_pcm_hardware_t snd_ymfpci_playback =
{
	chninfo:	(SND_PCM_CHNINFO_MMAP |
			 SND_PCM_CHNINFO_MMAP_VALID | 
			 SND_PCM_CHNINFO_STREAM |
			 SND_PCM_CHNINFO_BLOCK |
			 SND_PCM_CHNINFO_INTERLEAVE |
			 SND_PCM_CHNINFO_BLOCK_TRANSFER),
	formats:		SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,
	rates:			SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_48000,
	min_rate:		8000,
	max_rate:		48000,
	min_voices:		1,
	max_voices:		2,
	min_fragment_size:	32,
	max_fragment_size:	256 * 1024, /* XXX */
	fragment_align:		1,
	fifo_size:		0,
	transfer_block_size:	4,
	ioctl:			snd_ymfpci_playback_ioctl,
	prepare:		snd_ymfpci_playback_prepare,
	trigger:		snd_ymfpci_playback_trigger,
	pointer:		snd_ymfpci_playback_pointer,
};

static snd_pcm_hardware_t snd_ymfpci_capture =
{
	chninfo:		(SND_PCM_CHNINFO_MMAP |
				 SND_PCM_CHNINFO_MMAP_VALID |
				 SND_PCM_CHNINFO_STREAM |
				 SND_PCM_CHNINFO_BLOCK |
				 SND_PCM_CHNINFO_INTERLEAVE |
				 SND_PCM_CHNINFO_BLOCK_TRANSFER),
	formats:		SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,
	rates:			SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_48000,
	min_rate:		8000,
	max_rate:		48000,
	min_voices:		1,
	max_voices:		2,
	min_fragment_size:	32,
	max_fragment_size:	256 * 1024, /* XXX */
	fragment_align:		1,
	fifo_size:		0,
	transfer_block_size:	4,
	ioctl:			snd_ymfpci_capture_ioctl,
	prepare:		snd_ymfpci_capture_prepare,
	trigger:		snd_ymfpci_capture_trigger,
	pointer:		snd_ymfpci_capture_pointer,
};

static void snd_ymfpci_pcm_free_substream(void *private_data)
{
	ymfpci_pcm_t *ypcm = snd_magic_cast(ymfpci_pcm_t, private_data, );
	ymfpci_t *codec;
	
	if (ypcm) {
		codec = ypcm->codec;
		if (ypcm->voices[1])
			snd_ymfpci_voice_free(codec, ypcm->voices[1]);
		if (ypcm->voices[0])
			snd_ymfpci_voice_free(codec, ypcm->voices[0]);
		snd_magic_kfree(ypcm);
	}
}

static int snd_ymfpci_playback_open(void *private_data,
				    snd_pcm_subchn_t * substream)
{
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = substream->runtime;
	ymfpci_pcm_t *ypcm;
	int err;

	if ((err = snd_pcm_dma_alloc(substream, codec->dma1ptr, "YMFPCI - DAC")) < 0)
		return err;
	ypcm = snd_magic_kcalloc(ymfpci_pcm_t, 0, GFP_KERNEL);
	if (ypcm == NULL) {
		snd_pcm_dma_free(substream);
		return -ENOMEM;
	}
	ypcm->codec = codec;
	ypcm->type = PLAYBACK_VOICE;
	ypcm->substream = substream;
	runtime->hw = &snd_ymfpci_playback;
	snd_pcm_set_mixer(substream, codec->mixer->device, codec->ac97->me_playback);
	runtime->private_data = ypcm;
	runtime->private_free = snd_ymfpci_pcm_free_substream;
	return 0;
}

static int snd_ymfpci_playback_spdif_open(void *private_data,
					  snd_pcm_subchn_t * substream)
{
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = substream->runtime;
	ymfpci_pcm_t *ypcm;
	unsigned long flags;
	int err;
	
	if ((err = snd_ymfpci_playback_open(private_data, substream)) < 0)
		return err;
	ypcm = snd_magic_cast(ymfpci_pcm_t, runtime->private_data, 0);
	runtime->dig_mask = (snd_pcm_digital_t *) snd_kcalloc(sizeof(snd_pcm_digital_t), GFP_KERNEL);
	if (runtime->dig_mask == NULL) {
		snd_pcm_dma_free(substream);
		snd_kfree(ypcm);
		return -ENOMEM;
	}
	runtime->dig_mask_free = _snd_kfree;
	runtime->dig_mask->dig_valid = 1;
	runtime->dig_mask->dig_status[0] = 0x3e;
	runtime->dig_mask->dig_status[1] = 0xff;
	ypcm->spdif = 1;
	spin_lock_irqsave(&codec->reg_lock, flags);
	snd_ymfpci_writew(codec, YDSXGR_SPDIFOUTCTRL,
		snd_ymfpci_readw(codec, YDSXGR_SPDIFOUTCTRL) | 2);
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return 0;
}

static int snd_ymfpci_capture_open(void *private_data,
				   snd_pcm_subchn_t * substream,
				   u32 capture_bank_number)
{
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = substream->runtime;
	ymfpci_pcm_t *ypcm;
	int err;

	if ((err = snd_pcm_dma_alloc(substream, !capture_bank_number ? codec->dma2ptr : codec->dma3ptr, "YMFPCI - ADC")) < 0)
		return err;
	ypcm = snd_magic_kcalloc(ymfpci_pcm_t, 0, GFP_KERNEL);
	if (ypcm == NULL) {
		snd_pcm_dma_free(substream);
		return -ENOMEM;
	}
	ypcm->codec = codec;
	ypcm->type = capture_bank_number + CAPTURE_REC;
	ypcm->substream = substream;	
	ypcm->capture_bank_number = capture_bank_number;
	codec->capture_substream[capture_bank_number] = substream;
	runtime->hw = &snd_ymfpci_capture;
	snd_pcm_set_mixer(substream, codec->mixer->device, codec->ac97->me_capture);
	runtime->private_data = ypcm;
	runtime->private_free = snd_ymfpci_pcm_free_substream;
	snd_ymfpci_hw_start(codec);
	return 0;
}

static int snd_ymfpci_capture_rec_open(void *private_data,
				       snd_pcm_subchn_t * substream)
{
	return snd_ymfpci_capture_open(private_data, substream, 0);
}

static int snd_ymfpci_capture_ac97_open(void *private_data,
				        snd_pcm_subchn_t * substream)
{
	return snd_ymfpci_capture_open(private_data, substream, 1);
}

static int snd_ymfpci_playback_close(void *private_data,
				     snd_pcm_subchn_t * substream)
{
	// ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, -ENXIO);

	snd_pcm_dma_free(substream);
	return 0;
}

/* AES/IEC958 channel status bits */
#define SND_PCM_AES0_PROFESSIONAL	(1<<0)	/* 0 = consumer, 1 = professional */
#define SND_PCM_AES0_NONAUDIO		(1<<1)	/* 0 = audio, 1 = non-audio */
#define SND_PCM_AES0_PRO_EMPHASIS	(7<<2)	/* mask - emphasis */
#define SND_PCM_AES0_PRO_EMPHASIS_NOTID	(0<<2)	/* emphasis not indicated */
#define SND_PCM_AES0_PRO_EMPHASIS_NONE	(1<<2)	/* none emphasis */
#define SND_PCM_AES0_PRO_EMPHASIS_5015	(3<<2)	/* 50/15us emphasis */
#define SND_PCM_AES0_PRO_EMPHASIS_CCITT	(7<<2)	/* CCITT J.17 emphasis */
#define SND_PCM_AES0_PRO_FREQ_UNLOCKED	(1<<5)	/* source sample frequency: 0 = locked, 1 = unlocked */
#define SND_PCM_AES0_PRO_FS		(3<<6)	/* mask - sample frequency */
#define SND_PCM_AES0_PRO_FS_NOTID	(0<<6)	/* fs not indicated */
#define SND_PCM_AES0_PRO_FS_44100	(1<<6)	/* 44.1kHz */
#define SND_PCM_AES0_PRO_FS_48000	(2<<6)	/* 48kHz */
#define SND_PCM_AES0_PRO_FS_32000	(3<<6)	/* 32kHz */
#define SND_PCM_AES0_CON_NOT_COPYRIGHT	(1<<2)	/* 0 = copyright, 1 = not copyright */
#define SND_PCM_AES0_CON_EMPHASIS	(7<<3)	/* mask - emphasis */
#define SND_PCM_AES0_CON_EMPHASIS_NONE	(0<<3)	/* none emphasis */
#define SND_PCM_AES0_CON_EMPHASIS_5015	(1<<3)	/* 50/15us emphasis */
#define SND_PCM_AES0_CON_MODE		(3<<6)	/* mask - mode */
#define SND_PCM_AES1_PRO_MODE		(15<<0)	/* mask - channel mode */
#define SND_PCM_AES1_PRO_MODE_NOTID	(0<<0)	/* not indicated */
#define SND_PCM_AES1_PRO_MODE_STEREOPHONIC (2<<0) /* stereophonic - ch A is left */
#define SND_PCM_AES1_PRO_MODE_SINGLE	(4<<0)	/* single channel */
#define SND_PCM_AES1_PRO_MODE_TWO	(8<<0)	/* two channels */
#define SND_PCM_AES1_PRO_MODE_PRIMARY	(12<<0)	/* primary/secondary */
#define SND_PCM_AES1_PRO_MODE_BYTE3	(15<<0)	/* vector to byte 3 */
#define SND_PCM_AES1_PRO_USERBITS	(15<<4)	/* mask - user bits */
#define SND_PCM_AES1_PRO_USERBITS_NOTID	(0<<4)	/* not indicated */
#define SND_PCM_AES1_PRO_USERBITS_192	(8<<4)	/* 192-bit structure */
#define SND_PCM_AES1_PRO_USERBITS_UDEF	(12<<4)	/* user defined application */
#define SND_PCM_AES1_CON_CATEGORY	0x7f
#define SND_PCM_AES1_CON_GENERAL	0x00
#define SND_PCM_AES1_CON_EXPERIMENTAL	0x40
#define SND_PCM_AES1_CON_SOLIDMEM_MASK	0x0f
#define SND_PCM_AES1_CON_SOLIDMEM_ID	0x08
#define SND_PCM_AES1_CON_BROADCAST1_MASK 0x07
#define SND_PCM_AES1_CON_BROADCAST1_ID	0x04
#define SND_PCM_AES1_CON_DIGDIGCONV_MASK 0x07
#define SND_PCM_AES1_CON_DIGDIGCONV_ID	0x02
#define SND_PCM_AES1_CON_ADC_COPYRIGHT_MASK 0x1f
#define SND_PCM_AES1_CON_ADC_COPYRIGHT_ID 0x06
#define SND_PCM_AES1_CON_ADC_MASK	0x1f
#define SND_PCM_AES1_CON_ADC_ID		0x16
#define SND_PCM_AES1_CON_BROADCAST2_MASK 0x0f
#define SND_PCM_AES1_CON_BROADCAST2_ID	0x0e
#define SND_PCM_AES1_CON_LASEROPT_MASK	0x07
#define SND_PCM_AES1_CON_LASEROPT_ID	0x01
#define SND_PCM_AES1_CON_MUSICAL_MASK	0x07
#define SND_PCM_AES1_CON_MUSICAL_ID	0x05
#define SND_PCM_AES1_CON_MAGNETIC_MASK	0x07
#define SND_PCM_AES1_CON_MAGNETIC_ID	0x03
#define SND_PCM_AES1_CON_IEC908_CD	(SND_PCM_AES1_CON_LASEROPT_ID|0x00)
#define SND_PCM_AES1_CON_NON_IEC908_CD	(SND_PCM_AES1_CON_LASEROPT_ID|0x08)
#define SND_PCM_AES1_CON_PCM_CODER	(SND_PCM_AES1_CON_DIGDIGCONV_ID|0x00)
#define SND_PCM_AES1_CON_SAMPLER	(SND_PCM_AES1_CON_DIGDIGCONV_ID|0x20)
#define SND_PCM_AES1_CON_MIXER		(SND_PCM_AES1_CON_DIGDIGCONV_ID|0x10)
#define SND_PCM_AES1_CON_RATE_CONVERTER	(SND_PCM_AES1_CON_DIGDIGCONV_ID|0x18)
#define SND_PCM_AES1_CON_SYNTHESIZER	(SND_PCM_AES1_CON_MUSICAL_ID|0x00)
#define SND_PCM_AES1_CON_MICROPHONE	(SND_PCM_AES1_CON_MUSICAL_ID|0x08)
#define SND_PCM_AES1_CON_DAT		(SND_PCM_AES1_CON_MAGNETIC_ID|0x00)
#define SND_PCM_AES1_CON_VCR		(SND_PCM_AES1_CON_MAGNETIC_ID|0x08)
#define SND_PCM_AES1_CON_ORIGINAL	(1<<7)	/* this bits depends on the category code */
#define SND_PCM_AES2_PRO_SBITS		(7<<0)	/* mask - sample bits */
#define SND_PCM_AES2_PRO_SBITS_20	(2<<0)	/* 20-bit - coordination */
#define SND_PCM_AES2_PRO_SBITS_24	(4<<0)	/* 24-bit - main audio */
#define SND_PCM_AES2_PRO_SBITS_UDEF	(6<<0)	/* user defined application */
#define SND_PCM_AES2_PRO_WORDLEN	(7<<3)	/* mask - source word length */
#define SND_PCM_AES2_PRO_WORDLEN_NOTID	(0<<3)	/* not indicated */
#define SND_PCM_AES2_PRO_WORDLEN_22_18	(2<<3)	/* 22-bit or 18-bit */
#define SND_PCM_AES2_PRO_WORDLEN_23_19	(4<<3)	/* 23-bit or 19-bit */
#define SND_PCM_AES2_PRO_WORDLEN_24_20	(5<<3)	/* 24-bit or 20-bit */
#define SND_PCM_AES2_PRO_WORDLEN_20_16	(6<<3)	/* 20-bit or 16-bit */
#define SND_PCM_AES2_CON_SOURCE		(15<<0)	/* mask - source number */
#define SND_PCM_AES2_CON_SOURCE_UNSPEC	(0<<0)	/* unspecified */
#define SND_PCM_AES2_CON_CHANNEL	(15<<4)	/* mask - channel number */
#define SND_PCM_AES2_CON_CHANNEL_UNSPEC	(0<<4)	/* unspecified */
#define SND_PCM_AES3_CON_FS		(15<<0)	/* mask - sample frequency */
#define SND_PCM_AES3_CON_FS_44100	(0<<0)	/* 44.1kHz */
#define SND_PCM_AES3_CON_FS_48000	(2<<0)	/* 48kHz */
#define SND_PCM_AES3_CON_FS_32000	(3<<0)	/* 32kHz */
#define SND_PCM_AES3_CON_CLOCK		(3<<4)	/* mask - clock accuracy */
#define SND_PCM_AES3_CON_CLOCK_1000PPM	(0<<4)	/* 1000 ppm */
#define SND_PCM_AES3_CON_CLOCK_50PPM	(1<<4)	/* 50 ppm */
#define SND_PCM_AES3_CON_CLOCK_VARIABLE	(2<<4)	/* variable pitch */

static int snd_ymfpci_playback_spdif_close(void *private_data,
					   snd_pcm_subchn_t * substream)
{
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, -ENXIO);
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	snd_ymfpci_writew(codec, YDSXGR_SPDIFOUTCTRL,
		snd_ymfpci_readw(codec, YDSXGR_SPDIFOUTCTRL) & ~2);
	snd_ymfpci_writew(codec, YDSXGR_SPDIFOUTSTATUS,
		SND_PCM_AES0_CON_EMPHASIS_NONE |
		(SND_PCM_AES1_CON_ORIGINAL << 8) |
		(SND_PCM_AES1_CON_PCM_CODER << 8));
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return snd_ymfpci_playback_close(private_data, substream);
}

static int snd_ymfpci_capture_close(void *private_data,
				    snd_pcm_subchn_t * substream)
{
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = substream->runtime;
	ymfpci_pcm_t *ypcm = snd_magic_cast(ymfpci_pcm_t, runtime->private_data, -ENXIO);

	if (ypcm != NULL) {
		codec->capture_substream[ypcm->capture_bank_number] = NULL;
		snd_ymfpci_hw_stop(codec);
	}
	snd_pcm_dma_free(substream);
	return 0;
}

static void snd_ymfpci_pcm_free(void *private_data)
{
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, );
	codec->pcm = NULL;
}

int snd_ymfpci_pcm(ymfpci_t * codec, int device, snd_pcm_t ** rpcm)
{
	snd_pcm_t *pcm;
	int err;

	*rpcm = NULL;
	if ((err = snd_pcm_new(codec->card, "YMFPCI", device, 32, 1, &pcm)) < 0)
		return err;
	pcm->private_data = codec;
	pcm->private_free = snd_ymfpci_pcm_free;

	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].private_data = codec;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].open = snd_ymfpci_playback_open;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].close = snd_ymfpci_playback_close;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].private_data = codec;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].open = snd_ymfpci_capture_rec_open;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].close = snd_ymfpci_capture_close;

	/* global setup */
	pcm->info_flags = SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_CAPTURE |
			  SND_PCM_INFO_DUPLEX;
	strcpy(pcm->name, "YMFPCI");
	*rpcm = codec->pcm = pcm;
	return 0;
}

static void snd_ymfpci_pcm2_free(void *private_data)
{
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, );
	codec->pcm2 = NULL;
}

int snd_ymfpci_pcm2(ymfpci_t * codec, int device, snd_pcm_t ** rpcm)
{
	snd_pcm_t *pcm;
	int err;

	*rpcm = NULL;
	if ((err = snd_pcm_new(codec->card, "YMFPCI - AC'97", device, 0, 1, &pcm)) < 0)
		return err;
	pcm->private_data = codec;
	pcm->private_free = snd_ymfpci_pcm2_free;

	pcm->chn[SND_PCM_CHANNEL_CAPTURE].private_data = codec;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].open = snd_ymfpci_capture_ac97_open;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].close = snd_ymfpci_capture_close;

	/* global setup */
	pcm->info_flags = SND_PCM_INFO_CAPTURE;
	strcpy(pcm->name, "YMFPCI - AC'97");
	*rpcm = codec->pcm = pcm;
	return 0;
}

static void snd_ymfpci_pcm_spdif_free(void *private_data)
{
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, );
	codec->pcm_spdif = NULL;
}

int snd_ymfpci_pcm_spdif(ymfpci_t * codec, int device, snd_pcm_t ** rpcm)
{
	snd_pcm_t *pcm;
	int err;

	*rpcm = NULL;
	if ((err = snd_pcm_new(codec->card, "YMFPCI - S/PDIF", device, 1, 0, &pcm)) < 0)
		return err;
	pcm->private_data = codec;
	pcm->private_free = snd_ymfpci_pcm_spdif_free;

	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].private_data = codec;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].open = snd_ymfpci_playback_spdif_open;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].close = snd_ymfpci_playback_spdif_close;

	/* global setup */
	pcm->info_flags = SND_PCM_INFO_CAPTURE;
	strcpy(pcm->name, "YMFPCI - S/PDIF");
	*rpcm = codec->pcm = pcm;
	return 0;
}

/*
 *  Switches
 */

static int snd_ymfpci_get_spdif(snd_kmixer_t * mixer,
				snd_kswitch_t * kswitch,
				snd_switch_t * uswitch)
{
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, kswitch->private_data, -ENXIO);
	
	uswitch->type = SND_SW_TYPE_BOOLEAN;
	uswitch->value.enable = snd_ymfpci_readl(codec, YDSXGR_SPDIFOUTCTRL) & 1;
	return 0;
}

static int snd_ymfpci_set_spdif(snd_kmixer_t * mixer,
				snd_kswitch_t * kswitch,
				snd_switch_t * uswitch)
{
	unsigned long flags;
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, kswitch->private_data, -ENXIO);
	
	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	spin_lock_irqsave(&codec->reg_lock, flags);
	snd_ymfpci_writel(codec, YDSXGR_SPDIFOUTCTRL,
		(snd_ymfpci_readl(codec, YDSXGR_SPDIFOUTCTRL) & ~1) |
		(uswitch->value.enable ? 1 : 0));
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return 0;
}

static snd_kswitch_t snd_ymfpci_mixer_spdif =
{
	name:   "S/PDIF output",
	get:    (snd_get_switch_t *)snd_ymfpci_get_spdif,
	set:    (snd_set_switch_t *)snd_ymfpci_set_spdif,
};


/*
 * mixer elements for record source
 */

#define REC_VOL_MAX	16383

#define RECORD_VOLUME(ename,reg_offset)\
static int snd_ymfpci_volume_##ename(snd_kmixer_element_t *element, int w_flag, int *volume)\
{\
	ymfpci_t *codec;\
	int change = 0;\
	unsigned long flags;\
	unsigned int volreg, oldreg;\
	codec = snd_magic_cast(ymfpci_t, element->private_data, -ENXIO);\
	if (w_flag) {\
		volreg = (unsigned int)volume[0] | ((unsigned int)volume[1] << 16);\
		spin_lock_irqsave(&codec->reg_lock, flags);\
		oldreg = snd_ymfpci_readl(codec, reg_offset);\
		snd_ymfpci_writel(codec, reg_offset, volreg);\
		spin_unlock_irqrestore(&codec->reg_lock, flags);\
		change = (oldreg != volreg);\
	} else {\
		spin_lock_irqsave(&codec->reg_lock, flags);\
		volume[0] = snd_ymfpci_readw(codec, reg_offset);\
		volume[1] = snd_ymfpci_readw(codec, reg_offset + 2);\
		spin_unlock_irqrestore(&codec->reg_lock, flags);\
	}\
	return change;\
}\
static int snd_ymfpci_mixer_group_##ename(snd_kmixer_group_t *group,\
					  snd_kmixer_file_t *file, int w_flag,\
					  snd_mixer_group_t *ugroup)\
{\
	ymfpci_t *codec;\
	int voices[2];\
	int change = 0;\
	codec = snd_magic_cast(ymfpci_t, group->private_data, -ENXIO);\
	if (!w_flag) {\
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;\
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME;\
		ugroup->min = 0;\
		ugroup->max = REC_VOL_MAX;\
		ugroup->mute = 0;\
		snd_ymfpci_volume_##ename(codec->me_vol_##ename, 0, voices);\
		ugroup->volume.names.front_left = voices[0];\
		ugroup->volume.names.front_right = voices[1];\
	} else {\
		voices[0] = ugroup->volume.names.front_left;\
		voices[1] = ugroup->volume.names.front_right;\
		if (snd_ymfpci_volume_##ename(codec->me_vol_##ename, 1, voices)) {\
			snd_mixer_element_value_change_all_file(file, codec->me_vol_##ename, 0);\
			change = 1;\
		}\
	}\
	return change;\
}

RECORD_VOLUME(recsrc, YDSXGR_NATIVEDACINVOL)
RECORD_VOLUME(adcrec, YDSXGR_PRIADCLOOPVOL)
RECORD_VOLUME(spdifrec, YDSXGR_SPDIFLOOPVOL)


/*
 *  Mixer routines
 */

static void snd_ymfpci_mixer_free_ac97(void *private_data)
{
	ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, );
	codec->mixer = NULL;
}

int snd_ymfpci_mixer(ymfpci_t * codec, int device, snd_pcm_t * pcm, snd_kmixer_t ** rmixer)
{
	ac97_t ac97;
	snd_kmixer_t *mixer;
	snd_kmixer_group_t *group;
	static struct snd_mixer_element_volume1_range loopback_range[2] = {
		{0, REC_VOL_MAX, -3450, 0},
		{0, REC_VOL_MAX, -3450, 0}
	};
	int err;

	*rmixer = NULL;
	memset(&ac97, 0, sizeof(ac97));
	ac97.write = snd_ymfpci_codec_write;
	ac97.read = snd_ymfpci_codec_read;
	ac97.private_data = codec;
	ac97.private_free = snd_ymfpci_mixer_free_ac97;
	if ((err = snd_ac97_mixer(codec->card, device, &ac97, 1, &pcm->device, &mixer)) < 0)
		return err;
	codec->ac97 = snd_magic_cast(ac97_t, mixer->private_data, -ENXIO);
	snd_mixer_switch_new(mixer, &snd_ymfpci_mixer_spdif, codec);

	/* record source */
	if ((group = snd_mixer_lib_group_ctrl(mixer, "Capture Volume", 0, SND_MIXER_OSS_UNKNOWN,
					      snd_ymfpci_mixer_group_recsrc, codec)) == NULL)
		return -ENOMEM;
	if ((codec->me_vol_recsrc = snd_mixer_lib_volume1(mixer, "Capture Volume", 0, 2,
							  loopback_range, snd_ymfpci_volume_recsrc, codec)) == NULL)
		return -ENOMEM;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_recsrc) < 0)
		return -ENOMEM;
	/* AC97 -> record volume */
	if ((group = snd_mixer_lib_group_ctrl(mixer, "ADC Record", 0, SND_MIXER_OSS_UNKNOWN,
					      snd_ymfpci_mixer_group_adcrec, codec)) == NULL)
		return -ENOMEM;
	if ((codec->me_vol_adcrec = snd_mixer_lib_volume1(mixer, "ADC Record", 0, 2,
							  loopback_range, snd_ymfpci_volume_adcrec, codec)) == NULL)
		return -ENOMEM;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_adcrec) < 0 ||
	    /*snd_mixer_element_route_add(mixer, ac97.me_capture, codec->me_vol_adcrec) ||*/
	    snd_mixer_element_route_add(mixer, codec->me_vol_adcrec, codec->me_vol_recsrc))
		return -ENOMEM;
	/* SPDIF -> record volume */
	if ((group = snd_mixer_lib_group_ctrl(mixer, "SPDIF Record", 0, SND_MIXER_OSS_UNKNOWN,
					      snd_ymfpci_mixer_group_spdifrec, codec)) == NULL)
		return -ENOMEM;
	if ((codec->me_vol_spdifrec = snd_mixer_lib_volume1(mixer, "SPDIF Record", 0, 2,
							    loopback_range, snd_ymfpci_volume_spdifrec, codec)) == NULL)
		return -ENOMEM;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_spdifrec) < 0 ||
	    snd_mixer_element_route_add(mixer, codec->me_vol_spdifrec, codec->me_vol_recsrc))
		return -ENOMEM;

	*rmixer = codec->mixer = mixer;
	return 0;
}

/*
 *  proc interface
 */

static void snd_ymfpci_proc_read(snd_info_buffer_t * buffer,
				 void *private_data)
{
	// ymfpci_t *codec = snd_magic_cast(ymfpci_t, private_data, );
	
	snd_iprintf(buffer, "YMFPCI\n\n");
}

static int snd_ymfpci_proc_init(snd_card_t * card, ymfpci_t * codec)
{
	snd_info_entry_t *entry;
	
	entry = snd_info_create_entry(card, "ymfpci");
	if (entry) {
		entry->private_data = codec;
		entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
		entry->t.text.read_size = 4096;
		entry->t.text.read = snd_ymfpci_proc_read;
		if (snd_info_register(entry) < 0) {
			snd_info_unregister(entry);
			entry = NULL;
		}
	}
	codec->proc_entry = entry;
	return 0;
}

static int snd_ymfpci_proc_done(ymfpci_t * codec)
{
	if (codec->proc_entry)
		snd_info_unregister((snd_info_entry_t *) codec->proc_entry);
	return 0;
}

/*
 *  initialization routines
 */

static void snd_ymfpci_aclink_reset(struct pci_dev * pci)
{
	u8 cmd;

	pci_read_config_byte(pci, PCIR_DSXGCTRL, &cmd);
	if (cmd & 0x03) {
		pci_write_config_byte(pci, PCIR_DSXGCTRL, cmd & 0xfc);
		pci_write_config_byte(pci, PCIR_DSXGCTRL, cmd | 0x03);
		pci_write_config_byte(pci, PCIR_DSXGCTRL, cmd & 0xfc);
	}
}

static void snd_ymfpci_enable_dsp(ymfpci_t *codec)
{
	snd_ymfpci_writel(codec, YDSXGR_CONFIG, 0x00000001);
}

static void snd_ymfpci_disable_dsp(ymfpci_t *codec)
{
	u32 val;
	int timeout = 1000;

	val = snd_ymfpci_readl(codec, YDSXGR_CONFIG);
	if (val)
		snd_ymfpci_writel(codec, YDSXGR_CONFIG, 0x00000000);
	while (timeout-- > 0) {
		val = snd_ymfpci_readl(codec, YDSXGR_CONFIG);
		if ((val & 0x00000002) == 0)
			break;
	}
}

#include "ymfpci_image.h"

static void snd_ymfpci_download_image(ymfpci_t *codec)
{
	int i, ver_1e;
	u16 ctrl;

	snd_ymfpci_writel(codec, YDSXGR_NATIVEDACOUTVOL, 0x00000000);
	snd_ymfpci_disable_dsp(codec);
	snd_ymfpci_writel(codec, YDSXGR_MODE, 0x00010000);
	snd_ymfpci_writel(codec, YDSXGR_MODE, 0x00000000);
	snd_ymfpci_writel(codec, YDSXGR_MAPOFREC, 0x00000000);
	snd_ymfpci_writel(codec, YDSXGR_MAPOFEFFECT, 0x00000000);
	snd_ymfpci_writel(codec, YDSXGR_PLAYCTRLBASE, 0x00000000);
	snd_ymfpci_writel(codec, YDSXGR_RECCTRLBASE, 0x00000000);
	snd_ymfpci_writel(codec, YDSXGR_EFFCTRLBASE, 0x00000000);
	ctrl = snd_ymfpci_readw(codec, YDSXGR_GLOBALCTRL);
	snd_ymfpci_writew(codec, YDSXGR_GLOBALCTRL, ctrl & ~0x0007);

	/* setup DSP instruction code */
	for (i = 0; i < YDSXG_DSPLENGTH; i++)
		snd_ymfpci_writel(codec, YDSXGR_DSPINSTRAM + i, DspInst[i >> 2]);

	switch (codec->device_id) {
	case PCI_DEVICE_ID_YAMAHA_724F:
	case PCI_DEVICE_ID_YAMAHA_740C:
	case PCI_DEVICE_ID_YAMAHA_744:
	case PCI_DEVICE_ID_YAMAHA_754:
		ver_1e = 1;
		break;
	default:
		ver_1e = 0;
	}

	if (ver_1e) {
		/* setup control instruction code */
		for (i = 0; i < YDSXG_CTRLLENGTH; i++)
			snd_ymfpci_writel(codec, YDSXGR_CTRLINSTRAM + i, CntrlInst1E[i >> 2]);
	} else {
		for (i = 0; i < YDSXG_CTRLLENGTH; i++)
			snd_ymfpci_writel(codec, YDSXGR_CTRLINSTRAM + i, CntrlInst[i >> 2]);
	}

	snd_ymfpci_enable_dsp(codec);
}

static int snd_ymfpci_memalloc(ymfpci_t *codec)
{
	long size, playback_ctrl_size;
	int voice, bank;
	u8 *ptr;

	playback_ctrl_size = 4 + 4 * YDSXG_PLAYBACK_VOICES;
	codec->bank_size_playback = snd_ymfpci_readl(codec, YDSXGR_PLAYCTRLSIZE) << 2;
	codec->bank_size_capture = snd_ymfpci_readl(codec, YDSXGR_RECCTRLSIZE) << 2;
	codec->bank_size_effect = snd_ymfpci_readl(codec, YDSXGR_EFFCTRLSIZE) << 2;
	codec->work_size = YDSXG_DEFAULT_WORK_SIZE;
	
	size = ((playback_ctrl_size + 0x00ff) & ~0x00ff) +
	       ((codec->bank_size_playback * 2 * YDSXG_PLAYBACK_VOICES + 0x00ff) & ~0x00ff) +
	       ((codec->bank_size_capture * 2 * YDSXG_CAPTURE_VOICES + 0x00ff) & ~0x00ff) +
	       ((codec->bank_size_effect * 2 * YDSXG_EFFECT_VOICES + 0x00ff) & ~0x00ff) +
	       codec->work_size;
	/* FIXME: allocate memory in PCI 32-bit range on all platforms */
	ptr = (u8 *)snd_kcalloc((size + 0x00ff) & ~0x00ff, GFP_KERNEL);
	if (ptr == NULL)
		return -ENOMEM;
	codec->work_ptr = ptr;
	ptr += 0x00ff;
	(long)ptr &= ~0x00ff;

	codec->bank_base_playback = ptr;
	codec->ctrl_playback = (u32 *)ptr;
	codec->ctrl_playback[0] = YDSXG_PLAYBACK_VOICES;
	ptr += (playback_ctrl_size + 0x00ff) & ~0x00ff;
	for (voice = 0; voice < YDSXG_PLAYBACK_VOICES; voice++) {
		for (bank = 0; bank < 2; bank++) {
			codec->bank_playback[voice][bank] = (snd_ymfpci_playback_bank_t *)ptr;
			ptr += codec->bank_size_playback;
		}
		codec->voices[voice].number = voice;
		codec->voices[voice].bank = codec->bank_playback[voice][0];
	}
	ptr += (codec->bank_size_playback + 0x00ff) & ~0x00ff;
	codec->bank_base_capture = ptr;
	for (voice = 0; voice < YDSXG_CAPTURE_VOICES; voice++)
		for (bank = 0; bank < 2; bank++) {
			codec->bank_capture[voice][bank] = (snd_ymfpci_capture_bank_t *)ptr;
			ptr += codec->bank_size_capture;
		}
	ptr += (codec->bank_size_capture + 0x00ff) & ~0x00ff;
	codec->bank_base_effect = ptr;
	for (voice = 0; voice < YDSXG_EFFECT_VOICES; voice++)
		for (bank = 0; bank < 2; bank++) {
			codec->bank_effect[voice][bank] = (snd_ymfpci_effect_bank_t *)ptr;
			ptr += codec->bank_size_effect;
		}
	ptr += (codec->bank_size_effect + 0x00ff) & ~0x00ff;
	codec->work_base = ptr;

	snd_ymfpci_writel(codec, YDSXGR_PLAYCTRLBASE, virt_to_bus(codec->bank_base_playback));
	snd_ymfpci_writel(codec, YDSXGR_RECCTRLBASE, virt_to_bus(codec->bank_base_capture));
	snd_ymfpci_writel(codec, YDSXGR_EFFCTRLBASE, virt_to_bus(codec->bank_base_effect));
	snd_ymfpci_writel(codec, YDSXGR_WORKBASE, virt_to_bus(codec->work_base));
	snd_ymfpci_writel(codec, YDSXGR_WORKSIZE, codec->work_size >> 2);

	/* S/PDIF output initialization */
	snd_ymfpci_writew(codec, YDSXGR_SPDIFOUTCTRL, 0);
	snd_ymfpci_writew(codec, YDSXGR_SPDIFOUTSTATUS,
		SND_PCM_AES0_CON_EMPHASIS_NONE |
		(SND_PCM_AES1_CON_ORIGINAL << 8) |
		(SND_PCM_AES1_CON_PCM_CODER << 8));

	/* S/PDIF input initialization */
	snd_ymfpci_writew(codec, YDSXGR_SPDIFINCTRL, 0);

	/* move this volume setup to mixer */
	snd_ymfpci_writel(codec, YDSXGR_NATIVEDACOUTVOL, 0x3fff3fff);
	snd_ymfpci_writel(codec, YDSXGR_BUF441OUTVOL, 0x3fff3fff);
	snd_ymfpci_writel(codec, YDSXGR_NATIVEADCINVOL, 0x3fff3fff);
	snd_ymfpci_writel(codec, YDSXGR_NATIVEDACINVOL, 0x3fff3fff);

	return 0;
}

int snd_ymfpci_create(snd_card_t * card,
		      struct pci_dev * pci,
		      snd_dma_t * dma1ptr,
		      snd_dma_t * dma2ptr,
		      snd_dma_t * dma3ptr,
		      snd_irq_t * irqptr,
		      ymfpci_t ** rcodec)
{
	ymfpci_t *codec;
	int err;
	static snd_device_ops_t ops = {
		(snd_dev_free_t *)snd_ymfpci_free,
		NULL,
		NULL
	};
	
	*rcodec = NULL;
	codec = snd_magic_kcalloc(ymfpci_t, 0, GFP_KERNEL);
	if (codec == NULL)
		return -ENOMEM;
	spin_lock_init(&codec->reg_lock);
	spin_lock_init(&codec->voice_lock);
	codec->card = card;
	codec->pci = pci;
	codec->dma1ptr = dma1ptr;
	codec->dma2ptr = dma2ptr;
	codec->dma3ptr = dma3ptr;
	codec->irqptr = irqptr;
	codec->device_id = pci->device;
	pci_read_config_byte(pci, PCI_REVISION_ID, (u8 *)&codec->rev);
	codec->reg_area_phys = pci_resource_start(pci, 0);
	codec->reg_area_virt = (unsigned long)ioremap(codec->reg_area_phys, 0x8000);
	pci_set_master(pci);

	snd_ymfpci_aclink_reset(pci);
	if (snd_ymfpci_codec_ready(codec, 0, 1) < 0) {
		snd_ymfpci_free(codec);
		return -EIO;
	}

	snd_ymfpci_download_image(codec);

	udelay(100); /* seems we need some delay after downloading image.. */

	if (snd_ymfpci_memalloc(codec) < 0) {
		snd_ymfpci_free(codec);
		return -EIO;
	}

	snd_ymfpci_proc_init(card, codec);

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

	*rcodec = codec;
	return 0;
}

static int snd_ymfpci_free(ymfpci_t * codec)
{
	u16 ctrl;

	snd_debug_check(codec == NULL, -EINVAL);
	snd_ymfpci_proc_done(codec);

	snd_ymfpci_writel(codec, YDSXGR_NATIVEDACOUTVOL, 0);
	snd_ymfpci_writel(codec, YDSXGR_BUF441OUTVOL, 0);
	snd_ymfpci_writel(codec, YDSXGR_STATUS, ~0);
	snd_ymfpci_disable_dsp(codec);
	snd_ymfpci_writel(codec, YDSXGR_PLAYCTRLBASE, 0);
	snd_ymfpci_writel(codec, YDSXGR_RECCTRLBASE, 0);
	snd_ymfpci_writel(codec, YDSXGR_EFFCTRLBASE, 0);
	snd_ymfpci_writel(codec, YDSXGR_WORKBASE, 0);
	snd_ymfpci_writel(codec, YDSXGR_WORKSIZE, 0);
	ctrl = snd_ymfpci_readw(codec, YDSXGR_GLOBALCTRL);
	snd_ymfpci_writew(codec, YDSXGR_GLOBALCTRL, ctrl & ~0x0007);

	if (codec->reg_area_virt)
		iounmap((void *)codec->reg_area_virt);
	if (codec->work_ptr)
		snd_kfree(codec->work_ptr);
	snd_magic_kfree(codec);
	return 0;
}

EXPORT_SYMBOL(snd_ymfpci_create);
EXPORT_SYMBOL(snd_ymfpci_interrupt);
EXPORT_SYMBOL(snd_ymfpci_pcm);
EXPORT_SYMBOL(snd_ymfpci_pcm2);
EXPORT_SYMBOL(snd_ymfpci_pcm_spdif);
EXPORT_SYMBOL(snd_ymfpci_mixer);

/*
 *  INIT part
 */

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

static void __exit alsa_ymfpci_exit(void)
{
}

module_init(alsa_ymfpci_init)
module_exit(alsa_ymfpci_exit)
