rendered paste body/* 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();
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