/* Copyright (c) 1998-2003 VIA Technologies, Inc.

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, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTIES OR REPRESENTATIONS; 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.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

 *
 * Version: 2.66  
 *
 * Supported devices:(VT3074/VT3068) for kernel 2.4.x 2.6.x
 * The program is implemented for VIA Audio Devices: VT686/686A/686B/8231/8233/8235.
 *
 * Revision history
 *	Mar/2003		Original version 2.0
 *	Apr/2/2003		Add SCMS function: SNDCTL_DSP_GETSCMS & SNDCTL_DSP_SETSCMS
 *					(version change to 2.01).
 *	Apr/24/2003		ADD Smart5.1 function: SOUND_MIXER_3DSE
 *					(version change to 2.1).
 *	May/22/2003		Modify memory and Smart 5.1 function, Change original settings values, Add license note
 *					(version change to 2.11).
 *	May/27/2003	F01	Add AC3 Function, modify SPDIF.
 *					(version change to 2.12)
 *	Jun/6/2003	F02	Fixed MultiChannel issue.
 *					(version change to 2.13)
 *	Jun/15/2003	F03	Fixed full-duplex issue.
 *	Jun/23/2003		Add SPSR & CD2SP function: SNDCTL_DSP_SPSR & SNDCTL_DSP_CD2SP
 *					(version change to 2.20)
 *	Jul/8/2003	F04	Fixed some issues: 
 *					1.A garbage sound (bo bo!) before start and after stop playing
 *					2. Volume with VT1616A.
 *					3. Surround sound issue while set 2 speaker.
 *					(version change to 2.21)
 *	Jun/18/2003		Add EAPD function.	(version change to 2.22).
 *  Aug/20/2003		Fixed SPDIF issues(LinDVD). (version change to 2.3).
 *	Sep/19/2003		Support Suse8.2/Suse9.1(64bit) for K8T800, Fixed SPDIF issue of VT1617 Codec.
 *					(version chage to 2.41) 
 *	Apr/26/2004		Add SPDIF API control. (version change to 2.42)
 *  Jul/1/2004		Add mixer control for 6 channel. (version change to 2.43)
 * 					Support kernel 2.4 & 2.6.     (version change to 2.60)
 * Jul/27/2005      Fixed  poll_wait , lock whan play games(function "via_dsp_poll). (version change to 2.66)
 * Sep/9/2005       Fixed the function "pci_register_driver" return code for kernel 2.6.11 & 2.4.x 
*/


#define VIA_VERSION	0x020606		/*	version: 2.66 */	

#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
#include <linux/vermagic.h>
#endif
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/spinlock.h>
#include <linux/sound.h>
#include <linux/poll.h>
#include "soundcard.h"
#include "ac97_codec.h"
#include <linux/smp_lock.h>
#include <linux/ioport.h>
//#include <linux/wrapper.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
#include <asm/hardirq.h>
#else
#include <linux/interrupt.h>
#endif
#include <asm/semaphore.h>
#include "viaudio_regop.h"

/* user volume 0x4c4c, correspondingly ac97 code reigster 0x0707 */
/*	0x4c4c  --> 0x0707	0x4a4a --> 0x0808	*/
#define  DEFAULT_USER_AC97_VOLUME     0x4a4a   
#define VIA_SUPPORT_MMAP

#define assert(expr) \
        if(!(expr)) {					\
        printk( "Assertion failed! %s,%s,%s,line=%d\n",	\
        #expr,__FILE__,__FUNCTION__,__LINE__);		\
    }
    
#define VIA_MODULE_NAME "viaudio"
#define PFX		VIA_MODULE_NAME ": "


#define VIA_PCM_FMT_STEREO		(1<<4)		/* PCM stereo format if set */
#define VIA_PCM_FMT_16BIT		(1<<5)		/* PCM 16-bit format if set  */

#define VIA_MAX_BUFFER_DMA_PAGES	32		/* size of DMA buffers */
/* buffering default values in ms */
#define VIA_DEFAULT_BUFFER_TIME		500
#define VIA_DEFAULT_FRAG_TIME		20
#define VIA_MAX_FRAG_SIZE		PAGE_SIZE
#define VIA_MIN_FRAG_SIZE		1024
#define VIA_MIN_FRAG_NUMBER		2
#define VIA_MAX_FRAG_NUMBER		128

#ifndef AC97_PCM_LR_ADC_RATE
  #define AC97_PCM_LR_ADC_RATE AC97_PCM_LR_DAC_RATE
#endif
/* capabilities */
#ifdef VIA_SUPPORT_MMAP
#define VIA_DSP_CAP (DSP_CAP_REVISION | DSP_CAP_DUPLEX | DSP_CAP_MMAP | \
		     DSP_CAP_TRIGGER | DSP_CAP_REALTIME)
#else
#define VIA_DSP_CAP (DSP_CAP_REVISION | DSP_CAP_DUPLEX | \
		     DSP_CAP_TRIGGER | DSP_CAP_REALTIME)
#endif

#define SNDCTL_DSP_GETSCMS	_SIOR ('P',20, int)
#define SNDCTL_GET_EAPD		_SIOR ('P',21, int)
#define SNDCTL_DRIVER_VERSION	_SIOR('P', 22, int)

#define SNDCTL_DSP_SETSCMS	_SIOWR('P',20, int)
#define SNDCTL_DSP_SPSR		_SIOWR('P',21, int)
#define SNDCTL_DSP_CD2SP	_SIOWR('P',22, int)
#define SNDCTL_SET_EAPD		_SIOWR('P',23, int)
#define SNDCTL_SET_SPDIF	_SIOWR('P',24, int)

#define SPDIF			0x04
#define AC3			0x02

static int spdif_out=0;
MODULE_PARM(spdif_out,"i");
/* scatter-gather DMA table entry*/
struct via_sgd_table {
	u32 addr;
	u32 count;
};

enum via_channel_states {
	sgd_stopped = 0,
	sgd_in_progress = 1,
};


struct via_buffer_pgtbl {
	dma_addr_t handle;
	void *cpuaddr;
};


struct via_channel {
	atomic_t n_frags;
	atomic_t hw_ptr;
	wait_queue_head_t wait;

	unsigned int sw_ptr;
	unsigned int slop_len;
	unsigned int n_irqs;
	int bytes;

	unsigned is_active : 1;
	unsigned is_record : 1;
	unsigned is_3D : 1;
	unsigned is_mapped : 1;
	unsigned is_enabled : 1;

	u8 pcm_fmt;		
	unsigned rate;		
	unsigned int frag_size;
	unsigned int frag_number;

	volatile struct via_sgd_table *sgtable;
	dma_addr_t sgt_handle;

	unsigned int page_number;
	struct via_buffer_pgtbl pgtbl[VIA_MAX_BUFFER_DMA_PAGES];

	long iobase;
	const char *name;
};


/* data stored for each chip */
struct via_info {
	struct pci_dev *pdev;
	long baseaddr;

	struct ac97_codec ac97;
	spinlock_t lock;
	int card_num;		/* unique card number, from 0 */
	int dev_dsp;

//	unsigned rev_h : 1;
	unsigned using3D : 1;
	int locked_rate : 1;

	struct semaphore syscall_sem;
	struct semaphore open_sem;
	mode_t open_mode;
	wait_queue_head_t 	open_wait;
	
	struct via_channel ch_in;
	struct via_channel ch_out;
	struct via_channel ch_fm;
	
	struct via_channel ch_3D;
	u8 		n_channels;
	int	Ext_Capability;
	int	Ext_Function;
};


/* number of cards, used for assigning unique numbers to cards */
static unsigned via_num_cards = 0;
static unsigned via_IsVT3074 = 0;

/****************************************************************
 *
 * prototypes
 *
 *
 */

static int via_init_one (struct pci_dev *dev, const struct pci_device_id *id);
static void  via_remove_one (struct pci_dev *pdev);

static ssize_t via_dsp_read(struct file *file, char *buffer, size_t count, loff_t *ppos);
static ssize_t via_dsp_write(struct file *file, const char *buffer, size_t count, loff_t *ppos);
static unsigned int via_dsp_poll(struct file *file,  poll_table *wait);
static int via_dsp_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
static int via_dsp_open (struct inode *inode, struct file *file);
static int via_dsp_release(struct inode *inode, struct file *file);
static int via_dsp_mmap(struct file *file, struct vm_area_struct *vma);

static u16 via_ac97_read_reg (struct ac97_codec *codec, u8 reg);
static void via_ac97_write_reg (struct ac97_codec *codec, u8 reg, u16 value);
static u8 via_ac97_wait_idle (struct via_info *card);

static void via_chan_free (struct via_info *card, struct via_channel *chan);
static void via_chan_clear (struct via_info *card, struct via_channel *chan);
static void via_chan_buffer_free (struct via_info *card, struct via_channel *chan);

static void via_set_spdif_valid(struct via_info *card, int valid);
/****************************************************************
 *
 * Various data the driver needs
 *
 *
 */


static struct pci_device_id via_pci_tbl[] __initdata = {
     { PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C686_5, PCI_ANY_ID, PCI_ANY_ID,0,0,0 },
     { PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8233_5, PCI_ANY_ID, PCI_ANY_ID,0,0,1 },
	{ 0, }
};
MODULE_DEVICE_TABLE(pci,via_pci_tbl);


static struct pci_driver via_driver = {
	name:		VIA_MODULE_NAME,
	id_table:	via_pci_tbl,
	probe:		via_init_one,
	remove:		via_remove_one,
};


static void via_set_spdif_valid(struct via_info *card, int valid)
{
	u16 tmp16;
	
	/* firstly, temporarily disable SPDIF */
	tmp16 = via_ac97_read_reg(&card->ac97, AC97_EXTENDED_STATUS );
   	via_ac97_write_reg(&card->ac97, AC97_EXTENDED_STATUS, ((tmp16 & ~SPDIF ) | 1) );		
	/* Before sending PCM/AC3 data, makes the SPDIF valid */
	tmp16 = via_ac97_read_reg(&card->ac97, AC97_RESERVED_3A );
	if(valid)
		tmp16 &= ~0x8000;
	else
		tmp16 |= 0x8000;
	via_ac97_write_reg (&card->ac97, AC97_RESERVED_3A, tmp16 );
	/*	enable SPDIF */
	tmp16 = via_ac97_read_reg(&card->ac97, AC97_EXTENDED_STATUS );
    via_ac97_write_reg(&card->ac97, AC97_EXTENDED_STATUS, (tmp16 | 1 | SPDIF) );		
}

/**
 *	via_syscall_down - down the card-specific syscell semaphore
 *	@card: Private info for specified board
 *	@nonblock: boolean, non-zero if O_NONBLOCK is set
 *
 *	Returns zero for success.
 */

static inline int via_syscall_down (struct via_info *card, int nonblock)
{
	nonblock = 0;

	if (nonblock) {
		if (down_trylock (&card->syscall_sem))
			return -EAGAIN;
	} else {
		if (down_interruptible (&card->syscall_sem))
			return -ERESTARTSYS;
	}

	return 0;
}


/**
 *	via_stop_everything - Stop all audio operations
 *
 *	Stops all DMA operations and interrupts, and clear
 *	any pending status bits resulting from those operations.
 */

static void via_stop_everything (struct via_info *card)
{
	struct via_channel *tmpChan;
	
	DPRINTK ("ENTER\n");

	assert (card != NULL);
	if(via_IsVT3074)
		tmpChan = &card->ch_3D;
   	else
    	tmpChan = &card->ch_fm;
	/*
	 * terminate any existing operations on audio read/write channels
	 */
	via_chan_stop (card->ch_out.iobase);
	via_chan_stop (card->ch_in.iobase);
	via_chan_stop (tmpChan->iobase);		
		
	/*
	 * clear any existing stops / flags (sanity check mainly)
	 */
	via_chan_status_clear (card->ch_out.iobase);
	via_chan_status_clear (card->ch_in.iobase);
	via_chan_status_clear (tmpChan->iobase);
	/*
	 * clear any enabled interrupt bits
	 */
	clearInterBit(card->ch_out.iobase);
	clearInterBit(card->ch_in.iobase);
	clearInterBit(tmpChan->iobase);

	udelay(10);

	/*
	 * clear any existing flags
	 */
	via_chan_status_clear (card->ch_out.iobase);
	via_chan_status_clear (card->ch_in.iobase);
	via_chan_status_clear (tmpChan->iobase);

	DPRINTK ("EXIT\n");
}


/**
 *	via_set_rate - Set PCM sample rate for given channel
 *	@ac97: Pointer to generic codec info struct
 *	@chan: Private info for specified channel
 *	@rate: Desired PCM sample rate, in Khz (4000KHZ to 48000KHZ)
 */

static int via_set_rate (struct ac97_codec *ac97,
			 struct via_channel *chan, unsigned rate)
{
	struct via_info *card = ac97->private_data;
	int rate_reg;

	DPRINTK ("ENTER, rate = %d\n", rate);

	if (chan->rate == rate)
		goto out;
	if (card->locked_rate) {
		chan->rate = 48000;
		goto out;
	}

	if (rate > 48000)	rate = 48000;
	if (rate < 4000)	rate = 4000;

	rate_reg = chan->is_record ? AC97_PCM_LR_ADC_RATE :
			    AC97_PCM_FRONT_DAC_RATE;

	via_ac97_write_reg (ac97, AC97_POWER_CONTROL,
		(via_ac97_read_reg (ac97, AC97_POWER_CONTROL) & ~0x0200) |
		0x0200);
	if(chan->is_record){
		via_ac97_write_reg (ac97, 0x32, rate);
		via_ac97_write_reg (ac97, 0x34, rate);
	}
	else{
		via_ac97_write_reg (ac97, 0x2c, rate);
		via_ac97_write_reg (ac97, 0x2e, rate);
		via_ac97_write_reg (ac97, 0x30, rate);
	}
//	via_ac97_write_reg (ac97, rate_reg, rate);

	via_ac97_write_reg (ac97, AC97_POWER_CONTROL,
		via_ac97_read_reg (ac97, AC97_POWER_CONTROL) & ~0x0200);

	udelay (10);

	chan->rate = via_ac97_read_reg (ac97, rate_reg);

	if (chan->rate == 0) {
		card->locked_rate = 1;
		chan->rate = 48000;
		printk (KERN_WARNING PFX "Codec rate locked at 48Khz\n");
	}

out:
	DPRINTK ("EXIT, returning rate %d Hz\n", chan->rate);
	return chan->rate;
}


/****************************************************************
 * Channel-specific operations
 */


/*	via_chan_init_defaults - Initialize a struct via_channel */
 

static void via_chan_init_defaults (struct via_info *card, struct via_channel *chan)
{
	memset (chan, 0, sizeof (*chan));

	if (chan == &card->ch_out) {
		chan->name = "PCM-OUT";
		chan->iobase = getChanIobase(card->baseaddr, 0 );
	} else if (chan == &card->ch_in) {
		chan->name = "PCM-IN";
		chan->iobase = getChanIobase(card->baseaddr, 1 );
		chan->is_record = 1;
	} else if (chan == &card->ch_3D) {
		chan->name = "PCM-3D";
		chan->iobase = getChanIobase(card->baseaddr, 2 );;
		chan->is_3D = 1;
	} else if (chan == &card->ch_fm) {
		chan->name = "PCM-OUT-FM";
		chan->iobase = getChanIobase(card->baseaddr, 3 );;
	}else {
		//BUG();
	}

	init_waitqueue_head (&chan->wait);

	chan->pcm_fmt = getdefaultPcmFmtMask();
	chan->is_enabled = 1;

	chan->frag_number = 0;
	chan->frag_size = 0;
	atomic_set (&chan->n_frags, 0);
	atomic_set (&chan->hw_ptr, 0);
}

/*      via_chan_init - Initialize PCM channel */
static void via_chan_init (struct via_info *card, struct via_channel *chan)
{
	DPRINTK ("ENTER\n");

	via_chan_init_defaults (card, chan);

    /* stop any existing channel output */
	via_chan_clear (card, chan);
	via_chan_status_clear (chan->iobase);
	chan->pcm_fmt = via_chan_pcm_fmt (chan->iobase, chan->pcm_fmt, 1 , chan->is_record, chan->is_3D);

	DPRINTK ("EXIT\n");
}

/**
 *	via_chan_buffer_init - Initialize PCM channel buffer
 *	Performs some of the preparations necessary to begin a PCM channel.
 *	allocating the scatter-gather DMA table and buffers,
 *	passing the address of the DMA table to the hardware.
 */

static int via_chan_buffer_init (struct via_info *card, struct via_channel *chan)
{
	int page, offset;
	int i;

	if (chan->sgtable != NULL) {
		return 0;
	}

	/* alloc DMA-able memory for scatter-gather table */
	chan->sgtable = pci_alloc_consistent (card->pdev,
		(sizeof (struct via_sgd_table) * chan->frag_number),
		&chan->sgt_handle);
	if (!chan->sgtable) {
		printk (KERN_ERR PFX "DMA table alloc fail, aborting\n");
		DPRINTK ("EXIT\n");
		return -ENOMEM;
	}

	memset ((void*)chan->sgtable, 0,
		(sizeof (struct via_sgd_table) * chan->frag_number));

	/* alloc DMA-able memory for scatter-gather buffers */

	chan->page_number = (chan->frag_number * chan->frag_size) / PAGE_SIZE +
			    (((chan->frag_number * chan->frag_size) % PAGE_SIZE) ? 1 : 0);

	for (i = 0; i < chan->page_number; i++) {
		chan->pgtbl[i].cpuaddr = pci_alloc_consistent (card->pdev, PAGE_SIZE,
					      &chan->pgtbl[i].handle);

		if (!chan->pgtbl[i].cpuaddr) {
			chan->page_number = i;
			goto err_out_nomem;
		}

		DPRINTK ("dmabuf_pg #%d (h=%lx, v2p=%lx, a=%p)\n",
			i, (long)chan->pgtbl[i].handle,
			virt_to_phys(chan->pgtbl[i].cpuaddr),
			chan->pgtbl[i].cpuaddr);
	}

	for (i = 0; i < chan->frag_number; i++) {

		page = i / (PAGE_SIZE / chan->frag_size);
		offset = (i % (PAGE_SIZE / chan->frag_size)) * chan->frag_size;

		chan->sgtable[i].count = setSGDflag (chan->frag_size ,2);
		chan->sgtable[i].addr = cpu_to_le32 (chan->pgtbl[page].handle + offset);

		DPRINTK ("dmabuf #%d (32(h)=%lx)\n",
			 i,	(long)chan->sgtable[i].addr);
	}

	/* overwrite the last buffer information */
	chan->sgtable[chan->frag_number - 1].count = setSGDflag (chan->frag_size, 1);

	/* set location of DMA-able scatter-gather info table */
	DPRINTK ("outl (0x%X, 0x%04lX)\n",
		(unsigned int)chan->sgt_handle, getPcmTableAddr(chan->iobase));

	via_ac97_wait_idle (card);
	outl (chan->sgt_handle, getPcmTableAddr(chan->iobase));
	udelay (20);
	via_ac97_wait_idle (card);

	DPRINTK ("inl (0x%lX) = 0x%X\n",
		getPcmTableAddr(chan->iobase),
		inl(getPcmTableAddr(chan->iobase)));

	DPRINTK ("EXIT\n");
	return 0;

err_out_nomem:
	printk (KERN_ERR PFX "DMA buffer alloc fail, aborting\n");
	via_chan_buffer_free (card, chan);
	DPRINTK ("EXIT\n");
	return -ENOMEM;
}


/**
 *	via_chan_free - Release a PCM channel
 *	Performs all the functions necessary to clean up an initialized channel.
 */

static void via_chan_free (struct via_info *card, struct via_channel *chan)
{
	DPRINTK ("ENTER\n");

	spin_lock_irq (&card->lock);

	/* stop any existing channel output */
	via_chan_status_clear (chan->iobase);
	via_chan_stop (chan->iobase);
	via_chan_status_clear (chan->iobase);

	spin_unlock_irq (&card->lock);

	synchronize_irq(0);

	DPRINTK ("EXIT\n");
}

static void via_chan_buffer_free (struct via_info *card, struct via_channel *chan)
{
	int i;

	DPRINTK ("ENTER\n");

	/* zero location of DMA-able scatter-gather info table */
	via_ac97_wait_idle(card);
	outl (0, getPcmTableAddr(chan->iobase));

	for (i = 0; i < chan->page_number; i++)
		if (chan->pgtbl[i].cpuaddr) {
			pci_free_consistent (card->pdev, PAGE_SIZE,
					     chan->pgtbl[i].cpuaddr,
					     chan->pgtbl[i].handle);
			chan->pgtbl[i].cpuaddr = NULL;
			chan->pgtbl[i].handle = 0;
		}

	chan->page_number = 0;

	if (chan->sgtable) {
		pci_free_consistent (card->pdev,
			(sizeof (struct via_sgd_table) * chan->frag_number),
			(void*)chan->sgtable, chan->sgt_handle);
		chan->sgtable = NULL;
	}

	DPRINTK ("EXIT\n");
}


/*	via_chan_clear - Stop DMA channel operation, and reset pointers */

static void via_chan_clear (struct via_info *card, struct via_channel *chan)
{
	DPRINTK ("ENTER\n");
	via_chan_stop (chan->iobase);
	via_chan_buffer_free(card, chan);
	chan->is_active = 0;
	chan->is_mapped = 0;
	chan->is_enabled = 1;
	chan->slop_len = 0;
	chan->sw_ptr = 0;
	chan->n_irqs = 0;
	atomic_set (&chan->hw_ptr, 0);
	DPRINTK ("EXIT\n");
}


/**
 *	via_chan_set_speed - Set PCM sample rate for given channel
 *	@val: New sample rate, in Khz
 *	This function halts all audio operations for the given channel
 */

static int via_chan_set_speed (struct via_info *card,
			       struct via_channel *chan, int val)
{
	DPRINTK ("ENTER, requested rate = %d\n", val);

	via_chan_clear (card, chan);

	val = via_set_rate (&card->ac97, chan, val);

	DPRINTK ("EXIT, returning %d\n", val);
	return val;
}

/**
 *	via_chan_set_chans - Set the number of channels for 3D channel
 *	@val: the number of channels
 */
static int via_chan_set_chans(struct via_info *card,
			       struct via_channel *chan, int val)
{
	DPRINTK ("ENTER\n");

	if((val < 1) || (val > 6)) 
	{
		DPRINTK ("EXIT, with the wrong number of channels\n");
		return -EINVAL;
	}
	
	via_chan_clear (card, chan);
	
	if(chan->is_3D) {
		card->n_channels = val;
		Init3DChannel(chan->iobase, val);
	}
	
	DPRINTK ("EXIT\n");
	
	return val;
}

/**
 *	via_chan_set_fmt - Set PCM sample size for given channel
 *	@val: New sample size, use the %AFMT_xxx constants
 *	This function halts all audio operations for the given channel
 *	chan, and then calls via_chan_pcm_fmt to set the audio hardware
 *	to the new sample size, either 8-bit or 16-bit.
 */

static int via_chan_set_fmt (struct via_info *card,
			     struct via_channel *chan, int val)
{
	DPRINTK ("ENTER, val=%s\n",
		 val == AFMT_U8 ? "AFMT_U8" :
	 	 val == AFMT_S16_LE ? "AFMT_S16_LE" :
		 "unknown");

	via_chan_clear (card, chan);

	assert (val != AFMT_QUERY); /* this case is handled elsewhere */
	switch (val) {
	case AFMT_AC3:
	case AFMT_S16_LE:
		if ((chan->pcm_fmt & VIA_PCM_FMT_16BIT) == 0) {
			chan->pcm_fmt |= VIA_PCM_FMT_16BIT;
			chan->pcm_fmt = via_chan_pcm_fmt (chan->iobase, chan->pcm_fmt, 0 , chan->is_record, chan->is_3D);
		}
		break;

	case AFMT_U8:
		if (chan->pcm_fmt & VIA_PCM_FMT_16BIT) {
			chan->pcm_fmt &= ~VIA_PCM_FMT_16BIT;
			chan->pcm_fmt = via_chan_pcm_fmt (chan->iobase, chan->pcm_fmt, 0 , chan->is_record, chan->is_3D);
		}
		break;
	default:
		DPRINTK ("unknown AFMT: 0x%X\n", val);
		val = AFMT_S16_LE;
	}

	DPRINTK ("EXIT\n");
	return val;
}


/**
 *	via_chan_set_stereo - Enable or disable stereo for a DMA channel
 *	@val: New sample size, use the %AFMT_xxx constants
 */
static int via_chan_set_stereo (struct via_info *card,
			        struct via_channel *chan, int val)
{
	DPRINTK ("ENTER, channels = %d\n", val);

	via_chan_clear (card, chan);

	switch (val) {

	/* mono */
	case 1:
		chan->pcm_fmt &= ~VIA_PCM_FMT_STEREO;
		chan->pcm_fmt = via_chan_pcm_fmt (chan->iobase, chan->pcm_fmt, 0 , chan->is_record, chan->is_3D);
		break;

	/* stereo */
	case 2:
		chan->pcm_fmt |= VIA_PCM_FMT_STEREO;
		chan->pcm_fmt = via_chan_pcm_fmt (chan->iobase, chan->pcm_fmt, 0 , chan->is_record, chan->is_3D);
		break;

	/* unknown */
	default:
		printk (KERN_WARNING PFX "unknown number of channels\n");
		val = -EINVAL;
		break;
	}

	DPRINTK ("EXIT, returning %d\n", val);
	return val;
}

static int via_chan_set_buffering (struct via_info *card,
                                struct via_channel *chan, int val)
{
	int shift;

	/* in both cases the buffer cannot be changed */
	if (chan->is_active || chan->is_mapped) {
		return -EINVAL;
	}

	if (val < 0) {

		if (chan->frag_size && chan->frag_number)
			goto out;

		DPRINTK ("\n");

		chan->frag_size = (VIA_DEFAULT_FRAG_TIME * chan->rate *
				   ((chan->pcm_fmt & VIA_PCM_FMT_STEREO) ? 2 : 1) *
				   ((chan->pcm_fmt & VIA_PCM_FMT_16BIT) ? 2 : 1)) / 1000 - 1;

		shift = 0;
		while (chan->frag_size) {
			chan->frag_size >>= 1;
			shift++;
		}
		chan->frag_size = 1 << shift;

		chan->frag_number = (VIA_DEFAULT_BUFFER_TIME / VIA_DEFAULT_FRAG_TIME);

		DPRINTK ("setting default values %d %d\n", chan->frag_size, chan->frag_number);
	} else {
		chan->frag_size = 1 << (val & 0xFFFF);
		chan->frag_number = (val >> 16) & 0xFFFF;

		DPRINTK ("using user values %d %d\n", chan->frag_size, chan->frag_number);
	}

	/* quake3 wants frag_number to be a power of two */
	shift = 0;
	while (chan->frag_number) {
		chan->frag_number >>= 1;
		shift++;
	}
	chan->frag_number = 1 << shift;

	if (chan->frag_size > VIA_MAX_FRAG_SIZE)
		chan->frag_size = VIA_MAX_FRAG_SIZE;
	else if (chan->frag_size < VIA_MIN_FRAG_SIZE)
		chan->frag_size = VIA_MIN_FRAG_SIZE;
	
	if ((chan->frag_number * chan->frag_size) / PAGE_SIZE > VIA_MAX_BUFFER_DMA_PAGES)
		chan->frag_number = (VIA_MAX_BUFFER_DMA_PAGES * PAGE_SIZE) / chan->frag_size;
	
	if (chan->frag_number < VIA_MIN_FRAG_NUMBER)
                chan->frag_number = VIA_MIN_FRAG_NUMBER;
	
	if (chan->frag_number > VIA_MAX_FRAG_NUMBER)
                chan->frag_number = VIA_MAX_FRAG_NUMBER;

	DPRINTK ("using values %d %d\n", chan->frag_size, chan->frag_number);
out:
	if (chan->is_record)
		atomic_set (&chan->n_frags, 0);
	else
		atomic_set (&chan->n_frags, chan->frag_number);

//	DPRINTK ("EXIT\n");

	return 0;
}


/**
 *	via_chan_flush_frag - Flush partially-full playback buffer to hardware
 *	@chan: Channel whose DMA table will be displayed
 *
 *	Flushes partially-full playback buffer to hardware.
 */

static void via_chan_flush_frag (struct via_channel *chan)
{
	DPRINTK ("ENTER\n");

	assert (chan->slop_len > 0);

	if (chan->sw_ptr == (chan->frag_number - 1))
		chan->sw_ptr = 0;
	else
		chan->sw_ptr++;

	chan->slop_len = 0;

	assert (atomic_read (&chan->n_frags) > 0);
	atomic_dec (&chan->n_frags);

	DPRINTK ("EXIT\n");
}



/**
 *	via_chan_maybe_start - Initiate audio hardware DMA operation
 *
 *	Initiate DMA operation, if the DMA engine for the given
 *	channel @chan is not already active.
 */

static inline void via_chan_maybe_start (struct via_channel *chan)
{
	DPRINTK("is_actvie: %d, sg_acttive: %d is_enable %d\n", chan->is_active,sg_active(chan->iobase),chan->is_enabled);
//	assert (chan->is_active == sg_active(chan->iobase));

	if (!chan->is_active && chan->is_enabled) {
		chan->is_active = 1;
		sg_begin (chan->iobase);
		DPRINTK ("starting channel %s\n", chan->name);
	}
}


/****************************************************************
 *
 * Interface to ac97-codec module
 */

/**
 *	via_ac97_wait_idle - Wait until AC97 codec is not busy
 *	@card: Private info for specified board
 *
 *	Sleep until the AC97 codec is no longer busy.
 *	Returns the final value read from the SGD register being polled.
 */

static u8 via_ac97_wait_idle (struct via_info *card)
{
	DPRINTK ("ENTER/EXIT\n");

	assert (card != NULL);
	assert (card->pdev != NULL);

	return via_ac97_busy(card->baseaddr);
}


/**
 *	via_ac97_read_reg - Read AC97 standard register
 *	@codec: Pointer to generic AC97 codec info
 *	@reg: Index of AC97 register to be read
 *	Returns the 16-bit value stored in the specified
 *	register.
 */

static u16 via_ac97_read_reg (struct ac97_codec *codec, u8 reg)
{
	struct via_info *card;
	assert (codec != NULL);
	assert (codec->private_data != NULL);

	card = codec->private_data;
	return via_ac97_read_reg_ex(card->baseaddr, reg);
}


/**
 *	via_ac97_write_reg - Write AC97 standard register
 *	@codec: Pointer to generic AC97 codec info
 *	@reg: Index of AC97 register to be written
 *	@value: Value to be written to AC97 register
 */

static void via_ac97_write_reg (struct ac97_codec *codec, u8 reg, u16 value)
{
	struct via_info *card;
	
	DPRINTK ("ENTER\n");
	assert (codec != NULL);
	assert (codec->private_data != NULL);

	card = codec->private_data;
	via_ac97_write_reg_ex(card->baseaddr, reg, value);
	DPRINTK ("EXIT\n");
}


static int via_mixer_open (struct inode *inode, struct file *file)
{
	int minor = MINOR(inode->i_rdev);
	struct via_info *card;
	struct pci_dev *pdev;
	struct pci_driver *drvr;

	DPRINTK ("ENTER\n");
	
	pdev = NULL;
    minor = MINOR(inode->i_rdev);
    
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	pci_for_each_dev(pdev) {
#else
	while((pdev = pci_find_device(PCI_ANY_ID,PCI_ANY_ID,pdev)) !=NULL){
#endif
		drvr = pci_dev_driver (pdev);
		if (drvr == &via_driver) {
			assert (pci_get_drvdata (pdev) != NULL);

			card = pci_get_drvdata (pdev);
			if (card->ac97.dev_mixer == minor)
				goto match;
		}
	}

	DPRINTK ("EXIT, returning -ENODEV\n");
	return -ENODEV;

match:
	file->private_data = &card->ac97;

	DPRINTK ("EXIT, returning 0\n");
	return 0;
}

static int via_mixer_ioctl (struct inode *inode, struct file *file, unsigned int cmd,
			    unsigned long arg)
{
	struct ac97_codec *codec = file->private_data;
	struct via_info *card;
	int nonblock = (file->f_flags & O_NONBLOCK);
	int rc;

	DPRINTK ("ENTER\n");

	assert (codec != NULL);
	card = codec->private_data;
	assert (card != NULL);

	rc = via_syscall_down (card, nonblock);
	if (rc) goto out;

	rc = codec->mixer_ioctl(codec, cmd, arg);
//  F04 S
	if(cmd == SOUND_MIXER_WRITE_VOLUME){
		codec->mixer_ioctl(codec, SOUND_MIXER_WRITE_ALTPCM, arg);
	}
//  F04 E
	up (&card->syscall_sem);

out:
	DPRINTK ("EXIT, returning %d\n", rc);
	return rc;
}

static loff_t via_llseek(struct file *file, loff_t offset, int origin)
{
	DPRINTK ("ENTER\n");

	DPRINTK("EXIT, returning -ESPIPE\n");
	return -ESPIPE;
}

static struct file_operations via_mixer_fops = {
	owner:		THIS_MODULE,
	open:		via_mixer_open,
	llseek:		via_llseek,
	ioctl:		via_mixer_ioctl,
};


static int __init via_ac97_reset (struct via_info *card)
{
	struct pci_dev *pdev = card->pdev;
	u16 tmp16;

	DPRINTK ("ENTER\n");
	assert (pdev != NULL);

	via_ac97_reset_ex();

	if(via_IsVT3074 == 0 ){
		/* route FM trap to IRQ, disable FM trap */
		pci_write_config_byte (pdev, 0x48, 0x05);
		udelay(10);
		/* disable all codec GPI interrupts */
		outl (0, pci_resource_start (pdev, 0) + 0x8C);
	}

	/* enable variable rate */
 	tmp16 = via_ac97_read_reg (&card->ac97, AC97_EXTENDED_STATUS);
 	if ((tmp16 & 1) == 0)
 		via_ac97_write_reg (&card->ac97, AC97_EXTENDED_STATUS, tmp16 | 1);

	DPRINTK ("EXIT, returning 0\n");

	return 0;
}


static void via_ac97_codec_wait (struct ac97_codec *codec)
{
	assert (codec->private_data != NULL);
	via_ac97_wait_idle (codec->private_data);
}


static int __init via_ac97_init (struct via_info *card)
{
	int rc;
	u16 tmp16;
	unsigned int ExtReg;
	mm_segment_t fs;
	int val;
	
	DPRINTK ("ENTER\n");

	assert (card != NULL);

	memset (&card->ac97, 0, sizeof (card->ac97));
	card->ac97.private_data = card;
	card->ac97.codec_read = via_ac97_read_reg;
	card->ac97.codec_write = via_ac97_write_reg;
	card->ac97.codec_wait = via_ac97_codec_wait;

	card->ac97.dev_mixer = register_sound_mixer (&via_mixer_fops, -1);
	if (card->ac97.dev_mixer < 0) {
		printk (KERN_ERR PFX "unable to register AC97 mixer, aborting\n");
		DPRINTK ("EXIT, returning -EIO\n");
		return -EIO;
	}

	rc = via_ac97_reset (card);
	if (rc) {
		printk (KERN_ERR PFX "unable to reset AC97 codec, aborting\n");
		goto err_out;
	}

	if (ac97_probe_codec (&card->ac97) == 0) {
		printk (KERN_ERR PFX "unable to probe AC97 codec, aborting\n");
		rc = -EIO;
		goto err_out;
	}

	/* enable variable rate */
	tmp16 = via_ac97_read_reg (&card->ac97, AC97_EXTENDED_STATUS);
	via_ac97_write_reg (&card->ac97, AC97_EXTENDED_STATUS, tmp16 | 1);

 	/*
 	 * If we cannot enable VRA, we have a locked-rate codec.
 	 * We try again to enable VRA before assuming so, however.
 	 */
 	tmp16 = via_ac97_read_reg (&card->ac97, AC97_EXTENDED_STATUS);
 	if ((tmp16 & 1) == 0) {
 		via_ac97_write_reg (&card->ac97, AC97_EXTENDED_STATUS, tmp16 | 1);
 		tmp16 = via_ac97_read_reg (&card->ac97, AC97_EXTENDED_STATUS);
 		if ((tmp16 & 1) == 0) {
 			card->locked_rate = 1;
 			printk (KERN_WARNING PFX "Codec rate locked at 48Khz\n");
 		}
 	}
 	
	/*	check SPDIF */	
	ExtReg = via_ac97_read_reg(&card->ac97, AC97_EXTENDED_ID );
	card->Ext_Capability = 0 ;
	card->Ext_Function = 0 ;
	if(ExtReg & SPDIF) {
		card->Ext_Capability |= SPDIF ;
		card->Ext_Capability |= AC3 ;
	}
	/* firstly, disable SPDIF in Reg2A */
	if(tmp16 & SPDIF){
	   	card->Ext_Function &= ~SPDIF ;
	   	via_ac97_write_reg(&card->ac97, AC97_EXTENDED_STATUS, ((tmp16 & ~SPDIF ) | 1) );		
	}
	if((spdif_out == 1 ) && (ExtReg & SPDIF)) {
		/* clear valid bit in Reg3A */
		tmp16 = via_ac97_read_reg(&card->ac97, AC97_RESERVED_3A );
		via_ac97_write_reg (&card->ac97, AC97_RESERVED_3A, tmp16 | 0x8000);
		tmp16 = via_ac97_read_reg(&card->ac97, AC97_EXTENDED_STATUS );
        via_ac97_write_reg(&card->ac97, AC97_EXTENDED_STATUS, (tmp16 | 1 | SPDIF |0x8000) &(~0x0030) );
	   	card->Ext_Function |= SPDIF ;
		printk(KERN_INFO "via_ac97_init: Turn on SPDIF:spdif=%x,ExtReg=%x \n", spdif_out,ExtReg);
	}
	else 
		printk(KERN_INFO "via_ac97_init: ExtReg=%x \n", ExtReg);

	 /*
 	 * try to enable multichannels
 	 */
	if(via_IsVT3074) {
		if(ExtReg & 0x01C0) {
			tmp16 = via_ac97_read_reg(&card->ac97, AC97_EXTENDED_STATUS);
			via_ac97_write_reg (&card->ac97, AC97_EXTENDED_STATUS, tmp16 & ~0xF800);
			via_ac97_write_reg( &card->ac97, AC97_CENTER_LFE_MASTER, 0x0808 );
			via_ac97_write_reg( &card->ac97, AC97_SURROUND_MASTER, 0x0808);

			/* Test Control Register For VT1616 */
			via_ac97_write_reg( &card->ac97, 0x5A, 0x0230);
			ExtReg = via_ac97_read_reg(&card->ac97, AC97_EXTENDED_STATUS);
			DPRINTK ("ExtStatus	== %X\n", ExtReg);
		}
	}
	
	via_special_function(card->baseaddr);  // F04
	
	/* initialize the volume  */
	fs = get_fs();
	set_fs(KERNEL_DS);
	val = SOUND_MASK_MIC;
	card->ac97.mixer_ioctl(&card->ac97, SOUND_MIXER_WRITE_RECSRC, (unsigned long)&val);
	val= DEFAULT_USER_AC97_VOLUME;
	card->ac97.mixer_ioctl(&card->ac97, SOUND_MIXER_WRITE_RECLEV, (unsigned long)&val);
	card->ac97.mixer_ioctl(&card->ac97, SOUND_MIXER_WRITE_VOLUME, (unsigned long)&val);
	card->ac97.mixer_ioctl(&card->ac97, SOUND_MIXER_WRITE_PCM, (unsigned long)&val);
	card->ac97.mixer_ioctl(&card->ac97, SOUND_MIXER_WRITE_MIC, (unsigned long)&val);
	card->ac97.mixer_ioctl(&card->ac97, SOUND_MIXER_WRITE_LINE1, (unsigned long)&val);
	card->ac97.mixer_ioctl(&card->ac97, SOUND_MIXER_WRITE_CD, (unsigned long)&val);
	card->ac97.mixer_ioctl(&card->ac97, SOUND_MIXER_WRITE_LINE, (unsigned long)&val);
	card->ac97.mixer_ioctl(&card->ac97, SOUND_MIXER_WRITE_SYNTH,(unsigned long)&val);
	card->ac97.mixer_ioctl(&card->ac97, SOUND_MIXER_WRITE_LINE2, (unsigned long)&val);

	set_fs(fs);

	DPRINTK ("EXIT, returning 0\n");
	return 0;

err_out:
	unregister_sound_mixer (card->ac97.dev_mixer);
	DPRINTK ("EXIT, returning %d\n", rc);
	return rc;
}


static void via_ac97_cleanup (struct via_info *card)
{
	DPRINTK ("ENTER\n");

	assert (card != NULL);
	assert (card->ac97.dev_mixer >= 0);

	unregister_sound_mixer (card->ac97.dev_mixer);

	DPRINTK ("EXIT\n");
}



/****************************************************************
 * Interrupt-related code
 */

/**
 *	via_intr_channel - handle an interrupt for a single channel
 *	@chan: handle interrupt for this channel
 *
 *	Locking: inside card->lock
 */

static void via_intr_channel (struct via_channel *chan)
{
	int n;

	if( getInterStatus(chan->iobase) == 0 )
		return;
	if (!chan->sgtable) /* XXX: temporary solution */
		return;

	/* grab current h/w ptr value */
	n = atomic_read (&chan->hw_ptr);

	assert (n >= 0);
	assert (n < chan->frag_number);

	/* reset SGD data structure in memory to reflect a full buffer,
	 * and advance the h/w ptr, wrapping around to zero if needed
	 */
	if (n == (chan->frag_number - 1)) {
		chan->sgtable[n].count = setSGDflag(chan->frag_size, 1);
		atomic_set (&chan->hw_ptr, 0);
	} else {
		chan->sgtable[n].count = setSGDflag(chan->frag_size, 2);
		atomic_inc (&chan->hw_ptr);
	}

	chan->n_irqs++;
	chan->bytes += chan->frag_size;
	if (chan->bytes < 0) /* handle overflow of 31-bit value */
		chan->bytes = chan->frag_size;

	/* wake up anyone listening to see when interrupts occur */
	if (waitqueue_active (&chan->wait))
		wake_up_all (&chan->wait);

	if (chan->is_mapped)
		return;

	if (atomic_read (&chan->n_frags) < chan->frag_number)
		atomic_inc (&chan->n_frags);
	assert (atomic_read (&chan->n_frags) <= chan->frag_number);

	if (atomic_read (&chan->n_frags) == chan->frag_number) {
		chan->is_active = 0;
		via_chan_pause (chan->iobase);
	}

	DPRINTK ("%s intr, channel n_frags == %d\n", chan->name,
		 atomic_read (&chan->n_frags));
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
static void via_interrupt(int irq, void *dev_id, struct pt_regs *regs)
#else
static irqreturn_t via_interrupt(int irq, void *dev_id, struct pt_regs *regs)
#endif
{
	struct via_info *card = dev_id;
	int Intsource ;
	
	DPRINTK("interrupt , enter\n");
	Intsource = getInterruptSource(card->baseaddr);
	if( Intsource == 0 )
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)	
	 	return;
#else
		return IRQ_HANDLED;
#endif
	 	
	spin_lock (&card->lock);
	/* Intsource  1 : playback, 2: recording , 3: 3D*/
	if( Intsource == 1 ){	
		via_intr_channel (&card->ch_out);
/* F01 S */
		if((spdif_out == 1) && (card->Ext_Function & SPDIF) && (card->ch_out.is_active == 0 )){
			if(!(card->Ext_Function & AC3))
				via_set_spdif_valid(card, 0);
	     }
/* F01 E */
	}
	else if(Intsource == 2 )
		via_intr_channel (&card->ch_in);
	else if( Intsource == 3 ){
		via_intr_channel (&card->ch_3D);
/* F01 S */
		if((spdif_out == 1) && (card->Ext_Function & SPDIF) && (card->ch_3D.is_active == 0 )){
			if(!(card->Ext_Function & AC3))
				via_set_spdif_valid(card, 0);
	    }
/* F01 E */
	}
	else if( Intsource == 4 )
		via_intr_channel (&card->ch_fm);

	spin_unlock (&card->lock);
	DPRINTK("interrupt, exit\n");
	
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)	
	 	return;
#else
		return IRQ_HANDLED;
#endif	

}


static int via_interrupt_init (struct via_info *card)
{
	DPRINTK ("ENTER\n");
	
	assert (card != NULL);
	assert (card->pdev != NULL);

	/* check for sane IRQ number. can this ever happen? */
	if (card->pdev->irq < 2) {
		printk (KERN_ERR PFX "insane IRQ %d, aborting\n",
			card->pdev->irq);
		DPRINTK ("EXIT, returning -EIO\n");
		return -EIO;
	}
	if(via_IsVT3074 == 0 ) 
		via_SetFmTrapToNMI();

	if (request_irq (card->pdev->irq, via_interrupt, SA_SHIRQ, VIA_MODULE_NAME, card)) {
		printk (KERN_ERR PFX "unable to obtain IRQ %d, aborting\n",
			card->pdev->irq);
		DPRINTK ("EXIT, returning -EBUSY\n");
		return -EBUSY;
	}

	DPRINTK ("EXIT, returning 0\n");
	return 0;
}


/****************************************************************
 *
 * OSS DSP device
 *
 */

static struct file_operations via_dsp_fops = {
	owner:		THIS_MODULE,
	open:		via_dsp_open,
	release:	via_dsp_release,
	read:		via_dsp_read,
	write:		via_dsp_write,
	poll:		via_dsp_poll,
	llseek: 	via_llseek,
	ioctl:		via_dsp_ioctl,
	mmap:		via_dsp_mmap,
};


static int __init via_dsp_init (struct via_info *card)
{
	DPRINTK ("ENTER\n");

	assert (card != NULL);
	if(via_IsVT3074 == 0){
		/* turn off legacy features */
		via_turnoff_fm_sb();
	}
	via_stop_everything (card);

	card->dev_dsp = register_sound_dsp (&via_dsp_fops, -1);
	if (card->dev_dsp < 0) {
		DPRINTK ("EXIT, returning -ENODEV\n");
		return -ENODEV;
	}
	DPRINTK ("EXIT, returning 0\n");
	return 0;
}


static void via_dsp_cleanup (struct via_info *card)
{
	DPRINTK ("ENTER\n");

	assert (card != NULL);
	assert (card->dev_dsp >= 0);

	via_stop_everything (card);

	unregister_sound_dsp (card->dev_dsp);

	DPRINTK ("EXIT\n");
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
static struct page * via_mm_nopage (struct vm_area_struct * vma,
				    unsigned long address, int write_access)
#else
static struct page * via_mm_nopage (struct vm_area_struct * vma,
				    unsigned long address, int *write_access)
#endif
{
	struct via_info *card = vma->vm_private_data;
	struct via_channel *chan;
	struct page *dmapage;
	unsigned long pgoff;
	unsigned long max_bufs;
	int rd, wr;

	DPRINTK ("ENTER, start %lXh, ofs %lXh, pgoff %ld, addr %lXh\n",
		 vma->vm_start,
		 address - vma->vm_start,
		 (address - vma->vm_start) >> PAGE_SHIFT,
		 address
		 );

        if (address > vma->vm_end) {
		DPRINTK ("EXIT, returning NOPAGE_SIGBUS\n");
		return NOPAGE_SIGBUS; /* Disallow mremap */
	}
        if (!card) {
		DPRINTK ("EXIT, returning NOPAGE_OOM\n");
		return NOPAGE_OOM;	/* Nothing allocated */
	}

	if(card->using3D)
		chan = &card->ch_3D;
	else
		chan = &card->ch_out;
	
	pgoff = vma->vm_pgoff + ((address - vma->vm_start) >> PAGE_SHIFT);
	rd = card->ch_in.is_mapped;
	wr = chan->is_mapped;

	max_bufs = chan->frag_number;
	if (rd && wr) max_bufs *= 2;
	/* via_dsp_mmap() should ensure this */
	assert (pgoff < max_bufs);

	/* if full-duplex (read+write) and we have two sets of bufs,
	 * then the playback buffers come first, sez soundcard.c */
	if (pgoff >= chan->page_number) {
		pgoff -= chan->page_number;
		chan = &card->ch_in;
	} else if (!wr)
		chan = &card->ch_in;

	assert ((((unsigned long)chan->pgtbl[pgoff].cpuaddr) % PAGE_SIZE) == 0);

	dmapage = virt_to_page (chan->pgtbl[pgoff].cpuaddr);
	DPRINTK ("EXIT, returning page %p for cpuaddr %lXh\n",
		 dmapage, (unsigned long) chan->pgtbl[pgoff].cpuaddr);
	get_page (dmapage);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
	if(write_access)
		*write_access = VM_FAULT_MINOR;
#endif
	return dmapage;
}


#ifndef VM_RESERVED
static int via_mm_swapout (struct page *page, struct file *filp)
{
	return 0;
}
#endif /* VM_RESERVED */


struct vm_operations_struct via_mm_ops = {
	nopage:		via_mm_nopage,
#ifndef VM_RESERVED	
	swapout:	via_mm_swapout,
#endif
};


static int via_dsp_mmap(struct file *file, struct vm_area_struct *vma)
{
	struct via_info *card;
	int nonblock = (file->f_flags & O_NONBLOCK);
	int rc = -EINVAL, rd=0, wr=0;
	unsigned long max_size, size, start, offset;

	assert (file != NULL);
	assert (vma != NULL);
	card = file->private_data;
	assert (card != NULL);

	DPRINTK ("ENTER, start %lXh, size %ld, pgoff %ld\n",
		 vma->vm_start,
		 vma->vm_end - vma->vm_start,
		 vma->vm_pgoff);

	max_size = 0;
	if (vma->vm_flags & VM_READ) {
		rd = 1;
		via_chan_set_buffering(card, &card->ch_in, -1);
		via_chan_buffer_init (card, &card->ch_in);
		max_size += card->ch_in.page_number << PAGE_SHIFT;
	}
	if (vma->vm_flags & VM_WRITE) {
		wr = 1;
		if(card->using3D){
			via_chan_set_buffering(card, &card->ch_3D, -1);
			via_chan_buffer_init (card, &card->ch_3D);
			max_size += card->ch_3D.page_number << PAGE_SHIFT;
		}
		else {
			via_chan_set_buffering(card, &card->ch_out, -1);
			via_chan_buffer_init (card, &card->ch_out);
			max_size += card->ch_out.page_number << PAGE_SHIFT;
		}
	}

	start = vma->vm_start;
	offset = (vma->vm_pgoff << PAGE_SHIFT);
	size = vma->vm_end - vma->vm_start;

	/* some basic size/offset sanity checks */
	if (size > max_size)
		goto out;
	if (offset > max_size - size)
		goto out;

	rc = via_syscall_down (card, nonblock);
	if (rc) goto out;

	vma->vm_ops = &via_mm_ops;
	vma->vm_private_data = card;

#ifdef VM_RESERVED
	vma->vm_flags |= VM_RESERVED;
#endif

	if (rd)
		card->ch_in.is_mapped = 1;
	if (wr && card->using3D)
		card->ch_3D.is_mapped = 1;
	if (wr && !card->using3D)
		card->ch_out.is_mapped = 1;

	up (&card->syscall_sem);
	rc = 0;

out:
	DPRINTK ("EXIT, returning %d\n", rc);
	return rc;
}


static ssize_t via_dsp_do_read (struct via_info *card,
				char *userbuf, size_t count,
				int nonblock)
{
	DECLARE_WAITQUEUE(wait, current);
	const char *orig_userbuf = userbuf;
	struct via_channel *chan = &card->ch_in;
	size_t size;
	int n, tmp;
	ssize_t ret = 0;

	/* if SGD has not yet been started, start it */
	via_chan_maybe_start (chan);

handle_one_block:

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	if (current->need_resched) 
#else
	if(need_resched())
#endif
	{
		up(&card->syscall_sem);
		schedule ();
		ret = via_syscall_down (card, nonblock);
		if (ret)
			goto out;
	}

	/* grab current channel software pointer.  In the case of
	 * recording, this is pointing to the next buffer that
	 * will receive data from the audio hardware.
	 */
	n = chan->sw_ptr;
	add_wait_queue(&chan->wait, &wait);
	for (;;) {
		__set_current_state(TASK_INTERRUPTIBLE);
		tmp = atomic_read (&chan->n_frags);
		assert (tmp >= 0);
		assert (tmp <= chan->frag_number);
		if (tmp)
			break;
		if (nonblock || !chan->is_active) {
			ret = -EAGAIN;
			break;
		}

		up(&card->syscall_sem);

		DPRINTK ("Sleeping on block %d\n", n);
		schedule();

		ret = via_syscall_down (card, nonblock);
		if (ret)
			break;

		if (signal_pending (current)) {
			ret = -ERESTARTSYS;
			break;
		}
	}
	set_current_state(TASK_RUNNING);
	remove_wait_queue(&chan->wait, &wait);
	if (ret)
		goto out;

	/* Now that we have a buffer we can read from, send
	 * as much as sample data possible to userspace.
	 */
	while ((count > 0) && (chan->slop_len < chan->frag_size)) {
		size_t slop_left = chan->frag_size - chan->slop_len;
		void *base = chan->pgtbl[n / (PAGE_SIZE / chan->frag_size)].cpuaddr;
		unsigned ofs = n % (PAGE_SIZE / chan->frag_size)*chan->frag_size;

		size = (count < slop_left) ? count : slop_left;
		if (copy_to_user (userbuf,
				  base + ofs + chan->slop_len,
				  size)) {
			ret = -EFAULT;
			goto out;
		}

		count -= size;
		chan->slop_len += size;
		userbuf += size;
	}
	if (chan->slop_len < chan->frag_size)
		goto out;

	if (chan->sw_ptr == (chan->frag_number - 1))
		chan->sw_ptr = 0;
	else
		chan->sw_ptr++;

	/* mark one less buffer waiting to be processed */
	assert (atomic_read (&chan->n_frags) > 0);
	atomic_dec (&chan->n_frags);

	/* we are at a block boundary, there is no fragment data */
	chan->slop_len = 0;

	DPRINTK ("Flushed block %u, sw_ptr now %u, n_frags now %d\n",
		n, chan->sw_ptr, atomic_read (&chan->n_frags));

	if (count > 0)
		goto handle_one_block;

out:
	return (userbuf != orig_userbuf) ? (userbuf - orig_userbuf) : ret;
}


static ssize_t via_dsp_read(struct file *file, char *buffer, size_t count, loff_t *ppos)
{
	struct via_info *card;
	int nonblock = (file->f_flags & O_NONBLOCK);
	int rc;

	DPRINTK ("ENTER, file=%p, buffer=%p, count=%u, ppos=%lu\n",
		 file, buffer, (unsigned int)count, ppos ? ((unsigned long)*ppos) : 0);

	assert (file != NULL);
	assert (buffer != NULL);
	card = file->private_data;
	assert (card != NULL);

/*	if (ppos != &file->f_pos) {
		DPRINTK ("EXIT, returning -ESPIPE\n");
		return -ESPIPE;
	}
*/
	rc = via_syscall_down (card, nonblock);
	if (rc) goto out;

	if (card->ch_in.is_mapped) {
		rc = -ENXIO;
		goto out_up;
	}

	via_chan_set_buffering(card, &card->ch_in, -1);
        rc = via_chan_buffer_init (card, &card->ch_in);

	if (rc)
		goto out_up;

	rc = via_dsp_do_read (card, buffer, count, nonblock);

out_up:
	up (&card->syscall_sem);
out:
	DPRINTK ("EXIT, returning %ld\n",(long) rc);
	return rc;
}


static ssize_t via_dsp_do_write (struct via_info *card,
				 const char *userbuf, size_t count,
				 int nonblock)
{
    //   DECLARE_WAITQUEUE(wait, current);
    wait_queue_t wait;
	const char *orig_userbuf = userbuf;
	volatile struct via_sgd_table *sgtable;
	struct via_channel *chan;
	size_t size;
	int n, tmp;
	ssize_t ret = 0;

	if(card->using3D)
		chan = &card->ch_3D;
	else
		chan = &card->ch_out;

	sgtable = chan->sgtable;

	if(via_IsVT3074)
		setPcmStopIdx(chan->iobase, 0xff);
		
handle_one_block:
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	if (current->need_resched) 
#else
	if(need_resched())
#endif
	{
		up(&card->syscall_sem);
		schedule ();
		ret = via_syscall_down (card, nonblock);
		if (ret)
			goto out;
	}

	n = chan->sw_ptr;
	init_waitqueue_entry(&wait, current);
	add_wait_queue(&chan->wait, &wait);
	for (;;) {
		__set_current_state(TASK_INTERRUPTIBLE);
		tmp = atomic_read (&chan->n_frags);
		assert (tmp >= 0);
		assert (tmp <= chan->frag_number);
		if (tmp)
			break;
		if (nonblock || !chan->is_active) {
			ret = -EAGAIN;
			break;
		}

		up(&card->syscall_sem);

		DPRINTK ("Sleeping on page %d, tmp==%d, ir==%d\n", n, tmp, chan->is_record);
		schedule();

		ret = via_syscall_down (card, nonblock);
		if (ret)
			break;

		if (signal_pending (current)) {
			ret = -ERESTARTSYS;
			break;
		}
	}
	set_current_state(TASK_RUNNING);
	remove_wait_queue(&chan->wait, &wait);
	if (ret)
		goto out;

	/* Now that we have at least one fragment we can write to, fill the buffer
	 * as much as possible with data from userspace.
	 */
	while ((count > 0) && (chan->slop_len < chan->frag_size)) {
		size_t slop_left = chan->frag_size - chan->slop_len;

		size = (count < slop_left) ? count : slop_left;
		if (copy_from_user (chan->pgtbl[n / (PAGE_SIZE / chan->frag_size)].cpuaddr + (n % (PAGE_SIZE / chan->frag_size)) * chan->frag_size + chan->slop_len,
				    userbuf, size)) {
			ret = -EFAULT;
			goto out;
		}

		count -= size;
		chan->slop_len += size;
		userbuf += size;
	}

	if (chan->slop_len < chan->frag_size) {
		if(via_IsVT3074){
			sgtable[n].count = setSGDflag (chan->slop_len, 1);
			/* set stop index for VT3074*/
			setPcmStopIdx(chan->iobase, n);
		}
		else{
			sgtable[n].count = setSGDflag(chan->slop_len, 3);
		}
		goto out;
	}

	/* Record the true size for the audio hardware to notice */
        if (n == (chan->frag_number - 1))
                sgtable[n].count = setSGDflag (chan->frag_size, 1);
        else
                sgtable[n].count = setSGDflag (chan->frag_size, 2);

	if (chan->sw_ptr == (chan->frag_number - 1))
		chan->sw_ptr = 0;
	else
		chan->sw_ptr++;

	/* mark one less buffer as being available for userspace consumption */
	assert (atomic_read (&chan->n_frags) > 0);
	atomic_dec (&chan->n_frags);

	/* we are at a block boundary, there is no fragment data */
	chan->slop_len = 0;
/* F01 S */
	if((spdif_out == 1) && (card->Ext_Function & SPDIF) && (chan->is_active == 0)){
		if(!(card->Ext_Function & AC3))
			via_set_spdif_valid(card, 1);
	}
/* F01 E */
	/* if SGD has not yet been started, start it */
	via_chan_maybe_start (chan);

	DPRINTK ("Flushed block %u, sw_ptr now %u, n_frags now %d\n",
		n, chan->sw_ptr, atomic_read (&chan->n_frags));

	if (count > 0)
		goto handle_one_block;

out:
	return userbuf - orig_userbuf;
}


static ssize_t via_dsp_write(struct file *file, const char *buffer, size_t count, loff_t *ppos)
{
	struct via_info *card;
	ssize_t rc;
	int nonblock;

	assert(file!=NULL);
	nonblock = (file->f_flags & O_NONBLOCK);

//	DPRINTK (KERN_INFO " write ENTER, file=%p, buffer=%p, count=%u, ppos=%lu\n", file, buffer, count, ppos ? ((unsigned long)*ppos) : 0);
	assert (file != NULL);
	assert (buffer != NULL);
	
	card = file->private_data;
	assert (card != NULL);
    
/*zhf
	if (ppos != &file->f_pos) {
		DPRINTK ("EXIT, returning -ESPIPE\n");
		return -ESPIPE;
	}
*/

	rc = via_syscall_down (card, nonblock);
	if (rc) goto out;

	if (card->ch_out.is_mapped || card->ch_3D.is_mapped) {
		rc = -ENXIO;
		goto out_up;
	}
	
	if(card->using3D){
		via_chan_set_buffering(card, &card->ch_3D, -1);
		rc = via_chan_buffer_init (card, &card->ch_3D);
	}
	else{
		via_chan_set_buffering(card, &card->ch_out, -1);
		rc = via_chan_buffer_init (card, &card->ch_out);
	}

	if (rc)
		goto out_up;

	rc = via_dsp_do_write (card, buffer, count, nonblock);

out_up:
	up (&card->syscall_sem);
out:
//	printk(KERN_INFO "write EXIT, returning %ld\n",(long) rc);
	return rc;
}


static unsigned int via_dsp_poll(struct file *file,  poll_table *wait)
{
	struct via_info *card;
	struct via_channel *chan;
	unsigned int mask = 0;

//	printk (KERN_INFO "poll ENTER\n");
	assert (file != NULL);
	card = file->private_data;
	assert (card != NULL);
	
//	spin_lock_irq(&card->lock);
	if (file->f_mode & FMODE_READ) {
		chan = &card->ch_in;
		if (sg_active (chan->iobase))
		  if(atomic_read(&chan->n_frags) == 0 )
	          poll_wait(file, &chan->wait, wait);
		if (atomic_read (&chan->n_frags) > 0)
			mask |= POLLIN | POLLRDNORM;
	}

	if (file->f_mode & FMODE_WRITE) {
		if(card->using3D)
			chan = &card->ch_3D;
		else
			chan = &card->ch_out;
	
		if (sg_active (chan->iobase))
	        if(atomic_read(&chan->n_frags) == 0 )  
		      poll_wait(file, &chan->wait, wait);
		if ((atomic_read (&chan->n_frags) > 0) || (!chan->is_active))
			mask |= POLLOUT | POLLWRNORM;
	}
//	spin_unlock_irq(&card->lock);
//	printk (KERN_INFO " poll EXIT, returning %u\n", mask);
	return mask;
}


/**
 *	via_dsp_drain_playback - sleep until all playback samples are flushed
 *	@card: Private info for specified board
 *	@chan: Channel to drain
 *	@nonblock: boolean, non-zero if O_NONBLOCK is set
 *
 *	Sleeps until all playback has been flushed to the audio	hardware.
 *
 *	Locking: inside card->syscall_sem
 */

static int via_dsp_drain_playback (struct via_info *card,
				   struct via_channel *chan, int nonblock)
{
	DECLARE_WAITQUEUE(wait, current);
	int ret = 0;

	DPRINTK ("ENTER, nonblock = %d\n", nonblock);

	if (chan->slop_len > 0)
		via_chan_flush_frag (chan);

	if (atomic_read (&chan->n_frags) == chan->frag_number)
		goto out;

	via_chan_maybe_start (chan);

	add_wait_queue(&chan->wait, &wait);
	for (;;) {
		__set_current_state(TASK_INTERRUPTIBLE);
		if (atomic_read (&chan->n_frags) >= chan->frag_number)
			break;

		if (nonblock) {
			DPRINTK ("EXIT, returning -EAGAIN\n");
			ret = -EAGAIN;
			break;
		}

		up(&card->syscall_sem);

		DPRINTK ("sleeping, nbufs=%d\n", atomic_read (&chan->n_frags));
		schedule();

		if ((ret = via_syscall_down (card, nonblock)))
			break;

		if (signal_pending (current)) {
			DPRINTK ("EXIT, returning -ERESTARTSYS\n");
			ret = -ERESTARTSYS;
			break;
		}
	}
	set_current_state(TASK_RUNNING);
	remove_wait_queue(&chan->wait, &wait);

out:
	DPRINTK ("EXIT, returning %d\n", ret);
	return ret;
}


/**
 *	via_dsp_ioctl_space - get information about channel buffering
 *	Handles SNDCTL_DSP_GETISPACE and SNDCTL_DSP_GETOSPACE.
 */

static int via_dsp_ioctl_space (struct via_info *card,
				struct via_channel *chan,
				void *arg)
{
	audio_buf_info info;

	via_chan_set_buffering(card, chan, -1);

	info.fragstotal = chan->frag_number;
	info.fragsize = chan->frag_size;

	/* number of full fragments we can read/write without blocking */
	info.fragments = atomic_read (&chan->n_frags);

	if ((chan->slop_len % chan->frag_size > 0) && (info.fragments > 0))
		info.fragments--;

	/* number of bytes that can be read or written immediately
	 * without blocking.
	 */
	info.bytes = (info.fragments * chan->frag_size);
	if (chan->slop_len % chan->frag_size > 0)
		info.bytes += chan->frag_size - (chan->slop_len % chan->frag_size);

	DPRINTK ("EXIT, returning fragstotal=%d, fragsize=%d, fragments=%d, bytes=%d\n",
		info.fragstotal,
		info.fragsize,
		info.fragments,
		info.bytes);

	return copy_to_user (arg, &info, sizeof (info))?-EFAULT:0;
}


/**
 *	via_dsp_ioctl_ptr - get information about hardware buffer ptr
 *	Handles SNDCTL_DSP_GETIPTR and SNDCTL_DSP_GETOPTR.
 */

static int via_dsp_ioctl_ptr (struct via_info *card,
				struct via_channel *chan,
				void *arg)
{
	count_info info;

	spin_lock_irq (&card->lock);

	info.bytes = chan->bytes;
	info.blocks = chan->n_irqs;
	chan->n_irqs = 0;

	spin_unlock_irq (&card->lock);

	if (chan->is_active) {
		unsigned long extra;
		info.ptr = atomic_read (&chan->hw_ptr) * chan->frag_size;
		extra = chan->frag_size - getPcmBlockCount (chan->iobase );
		info.ptr += extra;
		info.bytes += extra;
	} else {
		info.ptr = 0;
	}

	DPRINTK ("EXIT, returning bytes=%d, blocks=%d, ptr=%d\n",
		info.bytes,
		info.blocks,
		info.ptr);

	return copy_to_user (arg, &info, sizeof (info))?-EFAULT:0;
}


static int via_dsp_ioctl_trigger (struct via_channel *chan, int val)
{
	int enable, do_something;

	if (chan->is_record)
		enable = (val & PCM_ENABLE_INPUT);
	else
		enable = (val & PCM_ENABLE_OUTPUT);

	if (!chan->is_enabled && enable) {
		do_something = 1;
	} else if (chan->is_enabled && !enable) {
		do_something = -1;
	} else {
		do_something = 0;
	}

	DPRINTK ("enable=%d, do_something=%d\n",
		 enable, do_something);

	if (chan->is_active && do_something)
		return -EINVAL;

	if (do_something == 1) {
		chan->is_enabled = 1;
		via_chan_maybe_start (chan);
		DPRINTK ("Triggering input\n");
	}

	else if (do_something == -1) {
		chan->is_enabled = 0;
		DPRINTK ("Setup input trigger\n");
	}

	return 0;
}


static int via_dsp_ioctl (struct inode *inode, struct file *file,
			  unsigned int cmd, unsigned long arg)
{
	int rc, rd=0, wr=0, val=0;
	u16 tmp16=0;
	struct via_info *card;
	struct via_channel *chan_out;
	int nonblock = (file->f_flags & O_NONBLOCK);

	assert (file != NULL);
	card = file->private_data;
	assert (card != NULL);

	if (file->f_mode & FMODE_WRITE)
		wr = 1;
	if (file->f_mode & FMODE_READ)
		rd = 1;

	if (card->using3D)
		chan_out = &card->ch_3D;
	else
		chan_out = &card->ch_out;
			
	rc = via_syscall_down (card, nonblock);
	if (rc)
		return rc;
	rc = -EINVAL;

	switch (cmd) {

	/* OSS API version.  XXX unverified */
	case OSS_GETVERSION:
		DPRINTK ("ioctl OSS_GETVERSION, EXIT, returning SOUND_VERSION\n");
		rc = put_user (SOUND_VERSION, (int *)arg);
		break;
	case SNDCTL_DRIVER_VERSION:
		rc = put_user (VIA_VERSION, (int *)arg);
		break;
	case SNDCTL_SET_SPDIF:
		if (get_user(val, (int *)arg)) {
			rc = -EFAULT;
			break;
		}
		if(val){
			if(card->Ext_Capability & SPDIF){
				spdif_out = 1;
				tmp16 = via_ac97_read_reg(&card->ac97, AC97_RESERVED_3A );
				via_ac97_write_reg (&card->ac97, AC97_RESERVED_3A, tmp16 | 0x8000);
				tmp16 = via_ac97_read_reg(&card->ac97, AC97_EXTENDED_STATUS );
		        via_ac97_write_reg(&card->ac97, AC97_EXTENDED_STATUS, (tmp16 | 1 | SPDIF |0x8000)&(~0x0030) );
			   	card->Ext_Function |= SPDIF ;			
			}
		}
		else{
			spdif_out = 0;
		   	card->Ext_Function &= ~SPDIF ;
		   	via_ac97_write_reg(&card->ac97, AC97_EXTENDED_STATUS, ((tmp16 & ~SPDIF ) | 1) );		
		}
		rc = 0;	
		break;
	/* list of supported PCM data formats */
	case SNDCTL_DSP_GETFMTS:
		DPRINTK ("DSP_GETFMTS, EXIT, returning AFMT U8|S16_LE\n");
		if(card->Ext_Capability & AC3) 
		 	rc = put_user(AFMT_S16_LE | AFMT_U8 | AFMT_AC3,(int *)arg);
		else
			rc = put_user (AFMT_U8 | AFMT_S16_LE, (int *)arg);
		break;
	/*	Set	EAPD bit	*/
	case SNDCTL_SET_EAPD:
		if (get_user(val, (int *)arg)) {
			rc = -EFAULT;
			break;
		}
		tmp16 = via_ac97_read_reg (&card->ac97, AC97_POWER_CONTROL);
		if(val)
			via_ac97_write_reg (&card->ac97, AC97_POWER_CONTROL, tmp16 | 0x8000);
		else
			via_ac97_write_reg (&card->ac97, AC97_POWER_CONTROL, tmp16 & (~0x8000));
		rc =0;
		break;		
	/*	Get	EAPD bit	*/
	case SNDCTL_GET_EAPD:
		tmp16 = via_ac97_read_reg (&card->ac97, AC97_POWER_CONTROL);
		rc = put_user (tmp16 & 0x8000, (int *)arg);		
		break;
		/* enable or disable smart5.1 function */
	case SOUND_MIXER_3DSE:
		if (get_user(val, (int *)arg)) {
			rc = -EFAULT;
			break;
		}
		rc =0;
		DPRINTK ("%s DSP Smart 5.1!\n", val? "Enable":"Disable");
		enableSmart51(card->baseaddr,val);
		break;	
	case SNDCTL_DSP_SPSR:
		if (get_user(val, (int *)arg)) {
			rc = -EFAULT;
			break;
		}
		tmp16 = via_ac97_read_reg (&card->ac97, AC97_RESERVED_3A);		
		val =  (tmp16 & (~0x3000)) | ((val & 0x3) << 12 );
		DPRINTK ("SNDCTL_DSP_SPSR:  AC97_RESERVED_3A: 0X%x, val: 0X%x\n", tmp16,val);	
		/* first disable SPDIF bit, then write R3A */	
		tmp16 = via_ac97_read_reg (&card->ac97, AC97_EXTENDED_STATUS);
		if( tmp16 & SPDIF)		
			via_ac97_write_reg (&card->ac97, AC97_EXTENDED_STATUS, tmp16 & (~SPDIF));
		via_ac97_write_reg (&card->ac97, AC97_RESERVED_3A, val);
		if( tmp16 & SPDIF)		
			via_ac97_write_reg (&card->ac97, AC97_EXTENDED_STATUS, tmp16 );
		rc = 0;
		break;
	case SNDCTL_DSP_CD2SP:
		if (get_user(val, (int *)arg)) {
			rc = -EFAULT;
			break;
		}
		tmp16 = via_ac97_read_reg (&card->ac97, 0X76);
		if(val)
			via_ac97_write_reg (&card->ac97, 0X76, tmp16 | 0x40);
		else
			via_ac97_write_reg (&card->ac97, 0X76, tmp16 & (~0x40));

		DPRINTK ("SNDCTL_DSP_CD2SP:  %s\n", val?"ON":"OFF");	
		rc = 0;					
		break;
	/* query 1st general bit" and "copyright" bit, CC[6-0]*/
	case SNDCTL_DSP_GETSCMS:
	 	tmp16 = via_ac97_read_reg (&card->ac97, AC97_RESERVED_3A);
		val = (tmp16 & 0x0FF4) >> 2;
		DPRINTK ("DSP_GETSCMS:  AC97_RESERVED_3A: 0X%x, val: 0X%x\n", tmp16,val);
		rc = put_user (val, (int *)arg);
		break;
	/*	set 1st general bit" and "copyright" bit, CC[6-0] */
	case SNDCTL_DSP_SETSCMS:
		if (get_user(val, (int *)arg) || val > 0X3ff) {
			rc = -EFAULT;
			break;
		}
		tmp16 = via_ac97_read_reg (&card->ac97, AC97_RESERVED_3A);		
		val =  (tmp16 & (~0x0FF4)) | ((val << 2) & 0x0FF4 );
		DPRINTK ("DSP_SETSCMS:  AC97_RESERVED_3A: 0X%x, val: 0X%x\n", tmp16,val);	
		/* first disable SPDIF bit, then write R3A */	
		tmp16 = via_ac97_read_reg (&card->ac97, AC97_EXTENDED_STATUS);
		if( tmp16 & SPDIF)		
			via_ac97_write_reg (&card->ac97, AC97_EXTENDED_STATUS, tmp16 & (~SPDIF));
		via_ac97_write_reg (&card->ac97, AC97_RESERVED_3A, val);
		if( tmp16 & SPDIF)		
			via_ac97_write_reg (&card->ac97, AC97_EXTENDED_STATUS, tmp16 );
		rc = 0;
		break;
	/* query or set current channel's PCM data format */
	case SNDCTL_DSP_SETFMT:
		if (get_user(val, (int *)arg)) {
			rc = -EFAULT;
			break;
		}
		DPRINTK ("DSP_SETFMT, val==%d\n", val);
		if (val != AFMT_QUERY) {
			rc = 0;

			if (rd)
				rc = via_chan_set_fmt (card, &card->ch_in, val);

			if (rc >= 0 && wr){
/*	F01 S */
				if (val == AFMT_AC3) {
				    if(card->Ext_Capability & AC3) {
			       		card->Ext_Function |= AC3 ;
		        	    tmp16 =  via_ac97_read_reg(&card->ac97, AC97_EXTENDED_STATUS);
			            via_ac97_write_reg(&card->ac97,AC97_EXTENDED_STATUS,(tmp16 | SPDIF));
			            tmp16 = via_ac97_read_reg(&card->ac97, AC97_RESERVED_3A);
			            via_ac97_write_reg(&card->ac97,AC97_RESERVED_3A,(tmp16 | AC3 | 0x8000));
					}
				}				
/*	F01 E */
				rc = via_chan_set_fmt (card, chan_out, val);			
			}
			if (rc < 0)
				break;

			val = rc;
		} else {
/* F01 S */		
			if( card->Ext_Function & AC3){
				card->Ext_Function &= ~AC3;
		    	tmp16 = via_ac97_read_reg(&card->ac97, AC97_RESERVED_3A );
	        	via_ac97_write_reg(&card->ac97, AC97_RESERVED_3A, (tmp16 & ~AC3));        
			    if( !(card->Ext_Function & SPDIF)) {
			      tmp16 = via_ac97_read_reg(&card->ac97, AC97_EXTENDED_STATUS );
			      via_ac97_write_reg(&card->ac97, AC97_EXTENDED_STATUS, (tmp16 & ~SPDIF) );
			    }
		    }
/* F01 E */		
			if ((rd && (card->ch_in.pcm_fmt & VIA_PCM_FMT_16BIT)) ||
			    (wr && chan_out->pcm_fmt & VIA_PCM_FMT_16BIT))
				val = AFMT_S16_LE;
			else
				val = AFMT_U8;
		}		
		DPRINTK ("SETFMT EXIT, returning %d\n", val);
                rc = put_user (val, (int *)arg);
		break;

	/* query or set number of channels (1=mono, 2=stereo) */
        case SNDCTL_DSP_CHANNELS:
		if (get_user(val, (int *)arg)) {
			rc = -EFAULT;
			break;
		}
		
		DPRINTK ("DSP_CHANNELS, val==%d\n", val);
		if (val != 0) {
			rc = 0;
			if (rd) 
				rc = via_chan_set_stereo (card, &card->ch_in, val);
/* F02 S */
			if(rc >= 0 && wr && val > 0 && val < 7 ){
				if( card->using3D == 1)
					rc = via_chan_set_chans (card, &card->ch_3D, val);			
				else
					rc = via_chan_set_stereo (card, &card->ch_out, val<2?1:2);					
			}
/* F02 E */
			if (rc < 0)
				break;
				
			val = rc;
		} 
		else {
			if (card->using3D == 1)
				val = card->n_channels;
			else 
				if ((rd && (card->ch_in.pcm_fmt & VIA_PCM_FMT_STEREO)) ||
						(wr && (card->ch_out.pcm_fmt & VIA_PCM_FMT_STEREO)))
					val = 2;
				else
					val = 1;
		}
		DPRINTK ("CHANNELS EXIT, returning %d\n", val);
              rc = put_user (val, (int *)arg);
		break;

	/* enable (val is not zero) or disable (val == 0) stereo */
        case SNDCTL_DSP_STEREO:
		if (get_user(val, (int *)arg)) {
			rc = -EFAULT;
			break;
		}
		DPRINTK ("DSP_STEREO, val==%d\n", val);
		rc = 0;
	
		if (rd)
			rc = via_chan_set_stereo (card, &card->ch_in, val ? 2 : 1);
		if (rc >= 0 && wr){
			if(card->using3D)
				rc = via_chan_set_chans (card, &card->ch_3D, val + 1);
			else{
				rc = via_chan_set_stereo (card, &card->ch_out, val ? 2 : 1);
			}
		}
		/*
		if (rc >= 0 && wr && !card->using3D)
			rc = via_chan_set_stereo (card, &card->ch_out, val ? 2 : 1);
		*/
		
		//if (rc >= 0 && wr )
		//	rc = via_chan_set_chans (card, &card->ch_3D, val + 1);

		if (rc < 0)
			break;

		val = rc - 1;

		DPRINTK ("STEREO EXIT, returning %d\n", val);
		rc = put_user(val, (int *) arg);
		break;

	/* query or set sampling rate */
        case SNDCTL_DSP_SPEED:
		if (get_user(val, (int *)arg)) {
			rc = -EFAULT;
			break;
		}
		
		DPRINTK ("DSP_SPEED, val==%d\n", val);
		if (val < 0) {
			rc = -EINVAL;
			break;
		}
		
		if (val > 0) {
			rc = 0;

			if (rd)
				rc = via_chan_set_speed (card, &card->ch_in, val);
			if (rc >= 0 && wr)
				rc = via_chan_set_speed (card, chan_out, val);

			if (rc < 0)
				break;

			val = rc;
		} 
		else {
			if (rd)
				val = card->ch_in.rate;
			else if (wr)
				val = chan_out->rate;
			else
				val = 0;
		}
		
		DPRINTK ("SPEED EXIT, returning %d\n", val);
                rc = put_user (val, (int *)arg);
		break;

	/* wait until all buffers have been played, and then stop device */
	case SNDCTL_DSP_SYNC:
		DPRINTK ("DSP_SYNC\n");
		rc = 0;

		if (wr) {
			DPRINTK ("SYNC EXIT (after calling via_dsp_drain_playback)\n");
			rc = via_dsp_drain_playback (card, chan_out, nonblock);
		}
		break;

	/* stop recording/playback immediately */
        case SNDCTL_DSP_RESET:
		DPRINTK ("DSP_RESET\n");
		if (rd) {
			via_chan_clear (card, &card->ch_in);
			card->ch_in.frag_number = 0;
			card->ch_in.frag_size = 0;
			atomic_set(&card->ch_in.n_frags, 0);
		}

		if (wr){
			via_chan_clear (card, chan_out);
			chan_out->frag_number = 0;
			chan_out->frag_size = 0;
			atomic_set(&chan_out->n_frags, 0);
		}				

		rc = 0;
		break;

	case SNDCTL_DSP_NONBLOCK:
		file->f_flags |= O_NONBLOCK;
		rc = 0;
		break;

	/* obtain bitmask of device capabilities, such as mmap, full duplex, etc. */
	case SNDCTL_DSP_GETCAPS:
		DPRINTK ("DSP_GETCAPS\n");
		rc = put_user(VIA_DSP_CAP, (int *)arg);
		break;

	/* obtain buffer fragment size */
	case SNDCTL_DSP_GETBLKSIZE:
		DPRINTK ("DSP_GETBLKSIZE\n");

		if (rd) {
			via_chan_set_buffering(card, &card->ch_in, -1);
			rc = put_user(card->ch_in.frag_size, (int *)arg);
		} 

		if (wr){
			via_chan_set_buffering(card, chan_out, -1);
			rc = put_user(chan_out->frag_size, (int *)arg);
		}				

		break;

	/* obtain information about input buffering */
	case SNDCTL_DSP_GETISPACE:
		DPRINTK ("DSP_GETISPACE\n");
		if (rd)
			rc = via_dsp_ioctl_space (card, &card->ch_in, (void*) arg);
		break;

	/* obtain information about output buffering */
	case SNDCTL_DSP_GETOSPACE:
		DPRINTK ("DSP_GETOSPACE\n");
		if (wr)
			rc = via_dsp_ioctl_space (card, chan_out, (void*) arg);
		break;

	/* obtain information about input hardware pointer */
	case SNDCTL_DSP_GETIPTR:
		DPRINTK ("DSP_GETIPTR\n");
		if (rd)
			rc = via_dsp_ioctl_ptr (card, &card->ch_in, (void*) arg);
		break;

	/* obtain information about output hardware pointer */
	case SNDCTL_DSP_GETOPTR:
		DPRINTK ("DSP_GETOPTR\n");
		if (wr)
			rc = via_dsp_ioctl_ptr (card, chan_out, (void*) arg);
		break;

	/* return number of bytes remaining to be played by DMA engine */
	case SNDCTL_DSP_GETODELAY:
		DPRINTK ("DSP_GETODELAY\n");

		if (!wr)
			break;

		if (chan_out->is_active) {

			val = chan_out->frag_number - atomic_read (&chan_out->n_frags);

			if (val > 0) {
				val *= chan_out->frag_size;
				val -= chan_out->frag_size - getPcmBlockCount (chan_out->iobase);
			}
			val += chan_out->slop_len % chan_out->frag_size;
		} else
			val = 0;

		assert (val <= (chan_out->frag_size * chan_out->frag_number));

		DPRINTK ("GETODELAY EXIT, val = %d bytes\n", val);
               rc = put_user (val, (int *)arg);
		break;

	/* handle the quick-start of a channel,
	 * or the notification that a quick-start will
	 * occur in the future
	 */
	case SNDCTL_DSP_SETTRIGGER:
		if (get_user(val, (int *)arg)) {
			rc = -EFAULT;
			break;
		}
		DPRINTK ("DSP_SETTRIGGER, rd=%d, wr=%d, act=%d/%d, en=%d/%d\n",
			rd, wr, card->ch_in.is_active, chan_out->is_active,
			card->ch_in.is_enabled, chan_out->is_enabled);

		rc = 0;

		if (rd)
			rc = via_dsp_ioctl_trigger (&card->ch_in, val);

		if (!rc && wr)
			rc = via_dsp_ioctl_trigger (chan_out, val);

		break;

	case SNDCTL_DSP_GETTRIGGER:
		val = 0;
		if ((file->f_mode & FMODE_READ) && card->ch_in.is_enabled)
			val |= PCM_ENABLE_INPUT;
		if ((file->f_mode & FMODE_WRITE) && chan_out->is_enabled)
			val |= PCM_ENABLE_OUTPUT;
		rc = put_user(val, (int *)arg);
		break;

	/* Enable full duplex.  Since we do this as soon as we are opened
	 * with O_RDWR, this is mainly a no-op that always returns success.
	 */
	case SNDCTL_DSP_SETDUPLEX:
		DPRINTK ("DSP_SETDUPLEX\n");
		if (!rd || !wr)
			break;
		rc = 0;
		break;

	/* set fragment size.  implemented as a successful no-op for now */
	case SNDCTL_DSP_SETFRAGMENT:
		if (get_user(val, (int *)arg)) {
			rc = -EFAULT;
			break;
		}
		DPRINTK ("DSP_SETFRAGMENT, val==%d\n", val);

		if (rd)
			rc = via_chan_set_buffering(card, &card->ch_in, val);

		if (wr)
			rc = via_chan_set_buffering(card, chan_out, val);

		DPRINTK ("SNDCTL_DSP_SETFRAGMENT (fragshift==0x%04X (%d), maxfrags==0x%04X (%d))\n",
			 val & 0xFFFF,
			 val & 0xFFFF,
			 (val >> 16) & 0xFFFF,
			 (val >> 16) & 0xFFFF);

		rc = 0;
		break;

	/* inform device of an upcoming pause in input (or output). */
	case SNDCTL_DSP_POST:
		DPRINTK ("DSP_POST\n");
		if (wr) {
			if (chan_out->slop_len > 0)
				via_chan_flush_frag (chan_out);
			via_chan_maybe_start (chan_out);
		}

		rc = 0;
		break;

	/* not implemented */
	default:
		DPRINTK ("unhandled ioctl, cmd==%u, arg==%p\n",
			 cmd, (void*) arg);
		break;
	}

	up (&card->syscall_sem);
	DPRINTK ("EXIT, returning %d\n", rc);
	return rc;
}


static int via_dsp_open (struct inode *inode, struct file *file)
{
	int minor;
	struct via_info *card;
	struct pci_dev *pdev;
	struct via_channel *chan;
	struct pci_driver *drvr;
	int nonblock;

	minor = MINOR(inode->i_rdev);
	nonblock = (file->f_flags & O_NONBLOCK);
	
	DPRINTK ("ENTER, minor=%d, file->f_mode=0x%x\n", minor, file->f_mode);

	if (!(file->f_mode & (FMODE_READ | FMODE_WRITE))) {
		DPRINTK ("EXIT, returning -EINVAL\n");
		return -EINVAL;
	}

	card = NULL;
	pdev = NULL;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	pci_for_each_dev(pdev) {
#else
	while((pdev = pci_find_device(PCI_ANY_ID,PCI_ANY_ID,pdev)) != NULL){
#endif
		drvr = pci_dev_driver (pdev);
		if (drvr == &via_driver) {
			assert (pci_get_drvdata (pdev) != NULL);

			card = pci_get_drvdata (pdev);
			DPRINTK ("dev_dsp = %d, minor = %d, assn = %d\n",
				 card->dev_dsp, minor,
				 (card->dev_dsp ^ minor) & ~0xf);

			if (((card->dev_dsp ^ minor) & ~0xf) == 0)
				goto match;
		}
	}

	DPRINTK ("no matching %s found\n", card ? "minor" : "driver");
	return -ENODEV;

match:
/* F03 S*/
	down(&card->open_sem);
	if(card->open_mode & file->f_mode){
		if (nonblock) {
			up(&card->open_sem);
			return -EBUSY;
		} else {
			up(&card->open_sem);
			interruptible_sleep_on(&card->open_wait);
			if (signal_pending(current))	return -ERESTARTSYS;
			down(&card->open_sem);
		}
   }
/* F03 E */   
	file->private_data = card;
	DPRINTK ("file->f_mode == 0x%x\n", file->f_mode);

	/* handle input from analog source */
	if (file->f_mode & FMODE_READ) {
		chan = &card->ch_in;

		via_chan_init (card, chan);

		/* why is this forced to 16-bit stereo in all drivers? */
		chan->pcm_fmt = VIA_PCM_FMT_16BIT | VIA_PCM_FMT_STEREO;

		chan->pcm_fmt = via_chan_pcm_fmt (chan->iobase, chan->pcm_fmt, 0 , chan->is_record, chan->is_3D);
		via_set_rate (&card->ac97, chan, 44100);
	}

	/* handle output to analog source */
	if (file->f_mode & FMODE_WRITE) {
		if (card->using3D)
			chan = &card->ch_3D;
		else
			chan = &card->ch_out;

		via_chan_init (card, chan);
		if (file->f_mode & FMODE_READ) {
			/* if in duplex mode make the recording and playback channels
			   have the same settings */
			chan->pcm_fmt = VIA_PCM_FMT_16BIT | VIA_PCM_FMT_STEREO;
			chan->pcm_fmt = via_chan_pcm_fmt (chan->iobase, chan->pcm_fmt, 0 , chan->is_record, chan->is_3D);
			via_set_rate (&card->ac97, chan, 44100);
		} else {
			 if ((minor & 0xf) == SND_DEV_DSP16) {
				chan->pcm_fmt = VIA_PCM_FMT_16BIT;
				chan->pcm_fmt = via_chan_pcm_fmt (chan->iobase, chan->pcm_fmt, 0 , chan->is_record, chan->is_3D);
				via_set_rate (&card->ac97, chan, 44100);
			} else {
				chan->pcm_fmt = via_chan_pcm_fmt (chan->iobase, chan->pcm_fmt, 1 , chan->is_record, chan->is_3D);
				via_set_rate (&card->ac97, chan, 8000);
			}
		}
	}
/* F03 S */
	card->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE);
	up(&card->open_sem);
/* F03 E */
	DPRINTK ("EXIT, returning 0\n");
	return 0;
}


static int via_dsp_release(struct inode *inode, struct file *file)
{
	struct via_info *card;
	struct via_channel *chan;
	int nonblock = (file->f_flags & O_NONBLOCK);
	int rc;
	u16	tmp16;

	DPRINTK ("ENTER\n");

	assert (file != NULL);
	card = file->private_data;
	assert (card != NULL);

	rc = via_syscall_down (card, nonblock);
	if (rc) {
		DPRINTK ("EXIT (syscall_down error), rc=%d\n", rc);
		return rc;
	}
	down(&card->open_sem);
	if (file->f_mode & FMODE_WRITE) {
		if (card->using3D)
			chan = &card->ch_3D;
		else
			chan = &card->ch_out;

		rc = via_dsp_drain_playback (card, chan, nonblock);
	//	if (rc)
	//		printk (KERN_DEBUG "via_audio: ignoring drain playback error %d\n", rc);

		via_chan_free (card, chan);
		via_chan_buffer_free(card, chan);
/* F01 S */		
		if( card->Ext_Function & AC3){
			card->Ext_Function &= ~AC3;
	    	tmp16 = via_ac97_read_reg(&card->ac97, AC97_RESERVED_3A );
        	via_ac97_write_reg(&card->ac97, AC97_RESERVED_3A, (tmp16 & ~AC3));
		    if( !(card->Ext_Function & SPDIF)) {
		      tmp16 = via_ac97_read_reg(&card->ac97, AC97_EXTENDED_STATUS );
		      via_ac97_write_reg(&card->ac97, AC97_EXTENDED_STATUS, (tmp16 & ~SPDIF) );
		    }
		}

		if((spdif_out == 1 ) && (card->Ext_Function & SPDIF)) 
			via_set_spdif_valid(card, 0);
		
/* F01 E */
	}

	if (file->f_mode & FMODE_READ) {
		via_chan_free (card, &card->ch_in);
		via_chan_buffer_free (card, &card->ch_in);
	}
/* F03 S */
	card->open_mode &= (~file->f_mode) & (FMODE_READ|FMODE_WRITE);
	up (&card->syscall_sem);
	up (&card->open_sem);
	wake_up(&card->open_wait);
/* F03 E */
	DPRINTK ("EXIT, returning 0\n");
	return 0;
}


/* Chip setup and kernel registration */

static int __init via_init_one (struct pci_dev *pdev, const struct pci_device_id *id)
{
	int rc;
	struct via_info *card;
	
	DPRINTK ("ENTER\n");
	if(id->device != PCI_DEVICE_ID_VIA_82C686_5 && id->device != PCI_DEVICE_ID_VIA_8233_5){
		printk(KERN_ERR PFX "Device ID(0X%X) not Supported\n", id->device);
		return -1;
	}
	rc = pci_enable_device (pdev);
	if (rc)
		goto err_out;

	rc = pci_request_regions (pdev, VIA_MODULE_NAME);
	if (rc)
		goto err_out_disable;

	card = kmalloc (sizeof (*card), GFP_KERNEL);
	if (!card) {
		printk (KERN_ERR PFX "out of memory, aborting\n");
		rc = -ENOMEM;
		goto err_out_res;
	}

	pci_set_drvdata (pdev, card);

	setPcidevBase(pdev->bus->number, pdev->devfn);
	memset (card, 0, sizeof (*card));
	card->pdev = pdev;
	card->baseaddr = pci_resource_start (pdev, 0);
	card->card_num = via_num_cards++;
	if(id->device == PCI_DEVICE_ID_VIA_8233_5){
		via_IsVT3074 = 1;
		via_set_card_attri(via_IsVT3074);
		card->n_channels = 2;
		card->using3D =1;	
	}
	if(id->device == PCI_DEVICE_ID_VIA_82C686_5)
		via_IsVT3074 = 0;
	
	spin_lock_init (&card->lock);
	init_MUTEX (&card->syscall_sem);
	init_MUTEX (&card->open_sem);
	init_waitqueue_head(&card->open_wait);
	card->open_mode =0;
	/* we must init these now, in case the intr handler needs them */
	via_chan_init_defaults (card, &card->ch_out);
	via_chan_init_defaults (card, &card->ch_in);
	if(via_IsVT3074)
		via_chan_init_defaults (card, &card->ch_3D);
	else
		via_chan_init_defaults (card, &card->ch_fm);

	if (pdev->irq < 1) {
		printk (KERN_ERR PFX "invalid PCI IRQ %d, aborting\n", pdev->irq);
		rc = -ENODEV;
		goto err_out_kfree;
	}

	if (!(pci_resource_flags (pdev, 0) & IORESOURCE_IO)) {
		printk (KERN_ERR PFX "unable to locate I/O resources, aborting\n");
		rc = -ENODEV;
		goto err_out_kfree;
	}

	/* init AC97 mixer and codec */
	rc = via_ac97_init (card);
	if (rc) {
		printk (KERN_ERR PFX "AC97 init failed, aborting\n");
		goto err_out_kfree;
	}

	/* init DSP device */
	rc = via_dsp_init (card);
	if (rc) {
		printk (KERN_ERR PFX "DSP device init failed, aborting\n");
		goto err_out_have_mixer;
	}

	/* init and turn on interrupts, as the last thing we do */
	rc = via_interrupt_init (card);
	if (rc) {
		printk (KERN_ERR PFX "interrupt init failed, aborting\n");
	}

	printk (KERN_INFO PFX "board(%s) #%d at 0x%04lX, IRQ %d\n", via_IsVT3074?"VT3074":"VT3068",
		card->card_num + 1, card->baseaddr, pdev->irq);

	DPRINTK ("EXIT, returning 0\n");
	return 0;

	via_dsp_cleanup (card);

err_out_have_mixer:
	via_ac97_cleanup (card);

err_out_kfree:

	kfree (card);

err_out_res:
	pci_release_regions (pdev);

err_out_disable:
	pci_disable_device (pdev);

err_out:
	pci_set_drvdata (pdev, NULL);
	DPRINTK ("EXIT - returning %d\n", rc);
	return rc;
}


static void __exit via_remove_one (struct pci_dev *pdev)
{
	struct via_info *card;

	DPRINTK ("ENTER\n");

	assert (pdev != NULL);
	card = pci_get_drvdata (pdev);
	assert (card != NULL);

	free_irq (card->pdev->irq, card);
	via_dsp_cleanup (card);
	via_ac97_cleanup (card);

	kfree (card);

	pci_set_drvdata (pdev, NULL);

	pci_release_regions (pdev);
	pci_disable_device (pdev);
	pci_set_power_state (pdev, 3); /* ...zzzzzz */

	DPRINTK ("EXIT\n");
	return;
}

static int __init init_via_audio(void)
{
	int rc;

	DPRINTK ("ENTER\n");
	
	rc = pci_register_driver (&via_driver);
	/* rc = 0  in kernel 2.6.11 , rc = 1 for others*/
	if (rc < 0) {      
		pci_unregister_driver (&via_driver);
		DPRINTK ("EXIT, returning -ENODEV\n");
		return -ENODEV;
	}

	DPRINTK ("EXIT, returning 0\n");
	return 0;
}


static void __exit cleanup_via_audio(void)
{
	DPRINTK ("ENTER\n");

	pci_unregister_driver (&via_driver);

	DPRINTK ("EXIT\n");
}


module_init(init_via_audio);
module_exit(cleanup_via_audio);

MODULE_AUTHOR("VIA");
MODULE_DESCRIPTION("DSP audio and mixer driver for VT 3074/3068 audio devices");
//MODULE_LICENSE("GPL");
//EXPORT_NO_SYMBOLS;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
MODULE_INFO(vermagic, VERMAGIC_STRING);
#endif

