linux/sound/pci/trident/trident_main.c

3971 lines
122 KiB
C
Raw Normal View History

/*
* Maintained by Jaroslav Kysela <perex@perex.cz>
* Originated by audio@tridentmicro.com
* Fri Feb 19 15:55:28 MST 1999
* Routines for control of Trident 4DWave (DX and NX) chip
*
* BUGS:
*
* TODO:
* ---
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* SiS7018 S/PDIF support by Thomas Winischhofer <thomas@winischhofer.net>
*/
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/gameport.h>
#include <linux/dma-mapping.h>
#include <sound/core.h>
#include <sound/info.h>
#include <sound/control.h>
#include <sound/tlv.h>
#include <sound/trident.h>
#include <sound/asoundef.h>
#include <asm/io.h>
static int snd_trident_pcm_mixer_build(struct snd_trident *trident,
struct snd_trident_voice * voice,
struct snd_pcm_substream *substream);
static int snd_trident_pcm_mixer_free(struct snd_trident *trident,
struct snd_trident_voice * voice,
struct snd_pcm_substream *substream);
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 13:55:46 +00:00
static irqreturn_t snd_trident_interrupt(int irq, void *dev_id);
static int snd_trident_sis_reset(struct snd_trident *trident);
static void snd_trident_clear_voices(struct snd_trident * trident,
unsigned short v_min, unsigned short v_max);
static int snd_trident_free(struct snd_trident *trident);
/*
* common I/O routines
*/
#if 0
static void snd_trident_print_voice_regs(struct snd_trident *trident, int voice)
{
unsigned int val, tmp;
printk("Trident voice %i:\n", voice);
outb(voice, TRID_REG(trident, T4D_LFO_GC_CIR));
val = inl(TRID_REG(trident, CH_LBA));
printk("LBA: 0x%x\n", val);
val = inl(TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC));
printk("GVSel: %i\n", val >> 31);
printk("Pan: 0x%x\n", (val >> 24) & 0x7f);
printk("Vol: 0x%x\n", (val >> 16) & 0xff);
printk("CTRL: 0x%x\n", (val >> 12) & 0x0f);
printk("EC: 0x%x\n", val & 0x0fff);
if (trident->device != TRIDENT_DEVICE_ID_NX) {
val = inl(TRID_REG(trident, CH_DX_CSO_ALPHA_FMS));
printk("CSO: 0x%x\n", val >> 16);
printk("Alpha: 0x%x\n", (val >> 4) & 0x0fff);
printk("FMS: 0x%x\n", val & 0x0f);
val = inl(TRID_REG(trident, CH_DX_ESO_DELTA));
printk("ESO: 0x%x\n", val >> 16);
printk("Delta: 0x%x\n", val & 0xffff);
val = inl(TRID_REG(trident, CH_DX_FMC_RVOL_CVOL));
} else { // TRIDENT_DEVICE_ID_NX
val = inl(TRID_REG(trident, CH_NX_DELTA_CSO));
tmp = (val >> 24) & 0xff;
printk("CSO: 0x%x\n", val & 0x00ffffff);
val = inl(TRID_REG(trident, CH_NX_DELTA_ESO));
tmp |= (val >> 16) & 0xff00;
printk("Delta: 0x%x\n", tmp);
printk("ESO: 0x%x\n", val & 0x00ffffff);
val = inl(TRID_REG(trident, CH_NX_ALPHA_FMS_FMC_RVOL_CVOL));
printk("Alpha: 0x%x\n", val >> 20);
printk("FMS: 0x%x\n", (val >> 16) & 0x0f);
}
printk("FMC: 0x%x\n", (val >> 14) & 3);
printk("RVol: 0x%x\n", (val >> 7) & 0x7f);
printk("CVol: 0x%x\n", val & 0x7f);
}
#endif
/*---------------------------------------------------------------------------
unsigned short snd_trident_codec_read(struct snd_ac97 *ac97, unsigned short reg)
Description: This routine will do all of the reading from the external
CODEC (AC97).
Parameters: ac97 - ac97 codec structure
reg - CODEC register index, from AC97 Hal.
returns: 16 bit value read from the AC97.
---------------------------------------------------------------------------*/
static unsigned short snd_trident_codec_read(struct snd_ac97 *ac97, unsigned short reg)
{
unsigned int data = 0, treg;
unsigned short count = 0xffff;
unsigned long flags;
struct snd_trident *trident = ac97->private_data;
spin_lock_irqsave(&trident->reg_lock, flags);
if (trident->device == TRIDENT_DEVICE_ID_DX) {
data = (DX_AC97_BUSY_READ | (reg & 0x000000ff));
outl(data, TRID_REG(trident, DX_ACR1_AC97_R));
do {
data = inl(TRID_REG(trident, DX_ACR1_AC97_R));
if ((data & DX_AC97_BUSY_READ) == 0)
break;
} while (--count);
} else if (trident->device == TRIDENT_DEVICE_ID_NX) {
data = (NX_AC97_BUSY_READ | (reg & 0x000000ff));
treg = ac97->num == 0 ? NX_ACR2_AC97_R_PRIMARY : NX_ACR3_AC97_R_SECONDARY;
outl(data, TRID_REG(trident, treg));
do {
data = inl(TRID_REG(trident, treg));
if ((data & 0x00000C00) == 0)
break;
} while (--count);
} else if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
data = SI_AC97_BUSY_READ | SI_AC97_AUDIO_BUSY | (reg & 0x000000ff);
if (ac97->num == 1)
data |= SI_AC97_SECONDARY;
outl(data, TRID_REG(trident, SI_AC97_READ));
do {
data = inl(TRID_REG(trident, SI_AC97_READ));
if ((data & (SI_AC97_BUSY_READ)) == 0)
break;
} while (--count);
}
if (count == 0 && !trident->ac97_detect) {
snd_printk(KERN_ERR "ac97 codec read TIMEOUT [0x%x/0x%x]!!!\n",
reg, data);
data = 0;
}
spin_unlock_irqrestore(&trident->reg_lock, flags);
return ((unsigned short) (data >> 16));
}
/*---------------------------------------------------------------------------
void snd_trident_codec_write(struct snd_ac97 *ac97, unsigned short reg,
unsigned short wdata)
Description: This routine will do all of the writing to the external
CODEC (AC97).
Parameters: ac97 - ac97 codec structure
reg - CODEC register index, from AC97 Hal.
data - Lower 16 bits are the data to write to CODEC.
returns: TRUE if everything went ok, else FALSE.
---------------------------------------------------------------------------*/
static void snd_trident_codec_write(struct snd_ac97 *ac97, unsigned short reg,
unsigned short wdata)
{
unsigned int address, data;
unsigned short count = 0xffff;
unsigned long flags;
struct snd_trident *trident = ac97->private_data;
data = ((unsigned long) wdata) << 16;
spin_lock_irqsave(&trident->reg_lock, flags);
if (trident->device == TRIDENT_DEVICE_ID_DX) {
address = DX_ACR0_AC97_W;
/* read AC-97 write register status */
do {
if ((inw(TRID_REG(trident, address)) & DX_AC97_BUSY_WRITE) == 0)
break;
} while (--count);
data |= (DX_AC97_BUSY_WRITE | (reg & 0x000000ff));
} else if (trident->device == TRIDENT_DEVICE_ID_NX) {
address = NX_ACR1_AC97_W;
/* read AC-97 write register status */
do {
if ((inw(TRID_REG(trident, address)) & NX_AC97_BUSY_WRITE) == 0)
break;
} while (--count);
data |= (NX_AC97_BUSY_WRITE | (ac97->num << 8) | (reg & 0x000000ff));
} else if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
address = SI_AC97_WRITE;
/* read AC-97 write register status */
do {
if ((inw(TRID_REG(trident, address)) & (SI_AC97_BUSY_WRITE)) == 0)
break;
} while (--count);
data |= SI_AC97_BUSY_WRITE | SI_AC97_AUDIO_BUSY | (reg & 0x000000ff);
if (ac97->num == 1)
data |= SI_AC97_SECONDARY;
} else {
address = 0; /* keep GCC happy */
count = 0; /* return */
}
if (count == 0) {
spin_unlock_irqrestore(&trident->reg_lock, flags);
return;
}
outl(data, TRID_REG(trident, address));
spin_unlock_irqrestore(&trident->reg_lock, flags);
}
/*---------------------------------------------------------------------------
void snd_trident_enable_eso(struct snd_trident *trident)
Description: This routine will enable end of loop interrupts.
End of loop interrupts will occur when a running
channel reaches ESO.
Also enables middle of loop interrupts.
Parameters: trident - pointer to target device class for 4DWave.
---------------------------------------------------------------------------*/
static void snd_trident_enable_eso(struct snd_trident * trident)
{
unsigned int val;
val = inl(TRID_REG(trident, T4D_LFO_GC_CIR));
val |= ENDLP_IE;
val |= MIDLP_IE;
if (trident->device == TRIDENT_DEVICE_ID_SI7018)
val |= BANK_B_EN;
outl(val, TRID_REG(trident, T4D_LFO_GC_CIR));
}
/*---------------------------------------------------------------------------
void snd_trident_disable_eso(struct snd_trident *trident)
Description: This routine will disable end of loop interrupts.
End of loop interrupts will occur when a running
channel reaches ESO.
Also disables middle of loop interrupts.
Parameters:
trident - pointer to target device class for 4DWave.
returns: TRUE if everything went ok, else FALSE.
---------------------------------------------------------------------------*/
static void snd_trident_disable_eso(struct snd_trident * trident)
{
unsigned int tmp;
tmp = inl(TRID_REG(trident, T4D_LFO_GC_CIR));
tmp &= ~ENDLP_IE;
tmp &= ~MIDLP_IE;
outl(tmp, TRID_REG(trident, T4D_LFO_GC_CIR));
}
/*---------------------------------------------------------------------------
void snd_trident_start_voice(struct snd_trident * trident, unsigned int voice)
Description: Start a voice, any channel 0 thru 63.
This routine automatically handles the fact that there are
more than 32 channels available.
Parameters : voice - Voice number 0 thru n.
trident - pointer to target device class for 4DWave.
Return Value: None.
---------------------------------------------------------------------------*/
void snd_trident_start_voice(struct snd_trident * trident, unsigned int voice)
{
unsigned int mask = 1 << (voice & 0x1f);
unsigned int reg = (voice & 0x20) ? T4D_START_B : T4D_START_A;
outl(mask, TRID_REG(trident, reg));
}
EXPORT_SYMBOL(snd_trident_start_voice);
/*---------------------------------------------------------------------------
void snd_trident_stop_voice(struct snd_trident * trident, unsigned int voice)
Description: Stop a voice, any channel 0 thru 63.
This routine automatically handles the fact that there are
more than 32 channels available.
Parameters : voice - Voice number 0 thru n.
trident - pointer to target device class for 4DWave.
Return Value: None.
---------------------------------------------------------------------------*/
void snd_trident_stop_voice(struct snd_trident * trident, unsigned int voice)
{
unsigned int mask = 1 << (voice & 0x1f);
unsigned int reg = (voice & 0x20) ? T4D_STOP_B : T4D_STOP_A;
outl(mask, TRID_REG(trident, reg));
}
EXPORT_SYMBOL(snd_trident_stop_voice);
/*---------------------------------------------------------------------------
int snd_trident_allocate_pcm_channel(struct snd_trident *trident)
Description: Allocate hardware channel in Bank B (32-63).
Parameters : trident - pointer to target device class for 4DWave.
Return Value: hardware channel - 32-63 or -1 when no channel is available
---------------------------------------------------------------------------*/
static int snd_trident_allocate_pcm_channel(struct snd_trident * trident)
{
int idx;
if (trident->ChanPCMcnt >= trident->ChanPCM)
return -1;
for (idx = 31; idx >= 0; idx--) {
if (!(trident->ChanMap[T4D_BANK_B] & (1 << idx))) {
trident->ChanMap[T4D_BANK_B] |= 1 << idx;
trident->ChanPCMcnt++;
return idx + 32;
}
}
return -1;
}
/*---------------------------------------------------------------------------
void snd_trident_free_pcm_channel(int channel)
Description: Free hardware channel in Bank B (32-63)
Parameters : trident - pointer to target device class for 4DWave.
channel - hardware channel number 0-63
Return Value: none
---------------------------------------------------------------------------*/
static void snd_trident_free_pcm_channel(struct snd_trident *trident, int channel)
{
if (channel < 32 || channel > 63)
return;
channel &= 0x1f;
if (trident->ChanMap[T4D_BANK_B] & (1 << channel)) {
trident->ChanMap[T4D_BANK_B] &= ~(1 << channel);
trident->ChanPCMcnt--;
}
}
/*---------------------------------------------------------------------------
unsigned int snd_trident_allocate_synth_channel(void)
Description: Allocate hardware channel in Bank A (0-31).
Parameters : trident - pointer to target device class for 4DWave.
Return Value: hardware channel - 0-31 or -1 when no channel is available
---------------------------------------------------------------------------*/
static int snd_trident_allocate_synth_channel(struct snd_trident * trident)
{
int idx;
for (idx = 31; idx >= 0; idx--) {
if (!(trident->ChanMap[T4D_BANK_A] & (1 << idx))) {
trident->ChanMap[T4D_BANK_A] |= 1 << idx;
trident->synth.ChanSynthCount++;
return idx;
}
}
return -1;
}
/*---------------------------------------------------------------------------
void snd_trident_free_synth_channel( int channel )
Description: Free hardware channel in Bank B (0-31).
Parameters : trident - pointer to target device class for 4DWave.
channel - hardware channel number 0-63
Return Value: none
---------------------------------------------------------------------------*/
static void snd_trident_free_synth_channel(struct snd_trident *trident, int channel)
{
if (channel < 0 || channel > 31)
return;
channel &= 0x1f;
if (trident->ChanMap[T4D_BANK_A] & (1 << channel)) {
trident->ChanMap[T4D_BANK_A] &= ~(1 << channel);
trident->synth.ChanSynthCount--;
}
}
/*---------------------------------------------------------------------------
snd_trident_write_voice_regs
Description: This routine will complete and write the 5 hardware channel
registers to hardware.
Parameters: trident - pointer to target device class for 4DWave.
voice - synthesizer voice structure
Each register field.
---------------------------------------------------------------------------*/
void snd_trident_write_voice_regs(struct snd_trident * trident,
struct snd_trident_voice * voice)
{
unsigned int FmcRvolCvol;
unsigned int regs[5];
regs[1] = voice->LBA;
regs[4] = (voice->GVSel << 31) |
((voice->Pan & 0x0000007f) << 24) |
((voice->CTRL & 0x0000000f) << 12);
FmcRvolCvol = ((voice->FMC & 3) << 14) |
((voice->RVol & 0x7f) << 7) |
(voice->CVol & 0x7f);
switch (trident->device) {
case TRIDENT_DEVICE_ID_SI7018:
regs[4] |= voice->number > 31 ?
(voice->Vol & 0x000003ff) :
((voice->Vol & 0x00003fc) << (16-2)) |
(voice->EC & 0x00000fff);
regs[0] = (voice->CSO << 16) | ((voice->Alpha & 0x00000fff) << 4) |
(voice->FMS & 0x0000000f);
regs[2] = (voice->ESO << 16) | (voice->Delta & 0x0ffff);
regs[3] = (voice->Attribute << 16) | FmcRvolCvol;
break;
case TRIDENT_DEVICE_ID_DX:
regs[4] |= ((voice->Vol & 0x000003fc) << (16-2)) |
(voice->EC & 0x00000fff);
regs[0] = (voice->CSO << 16) | ((voice->Alpha & 0x00000fff) << 4) |
(voice->FMS & 0x0000000f);
regs[2] = (voice->ESO << 16) | (voice->Delta & 0x0ffff);
regs[3] = FmcRvolCvol;
break;
case TRIDENT_DEVICE_ID_NX:
regs[4] |= ((voice->Vol & 0x000003fc) << (16-2)) |
(voice->EC & 0x00000fff);
regs[0] = (voice->Delta << 24) | (voice->CSO & 0x00ffffff);
regs[2] = ((voice->Delta << 16) & 0xff000000) |
(voice->ESO & 0x00ffffff);
regs[3] = (voice->Alpha << 20) |
((voice->FMS & 0x0000000f) << 16) | FmcRvolCvol;
break;
default:
snd_BUG();
return;
}
outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
outl(regs[0], TRID_REG(trident, CH_START + 0));
outl(regs[1], TRID_REG(trident, CH_START + 4));
outl(regs[2], TRID_REG(trident, CH_START + 8));
outl(regs[3], TRID_REG(trident, CH_START + 12));
outl(regs[4], TRID_REG(trident, CH_START + 16));
#if 0
printk("written %i channel:\n", voice->number);
printk(" regs[0] = 0x%x/0x%x\n", regs[0], inl(TRID_REG(trident, CH_START + 0)));
printk(" regs[1] = 0x%x/0x%x\n", regs[1], inl(TRID_REG(trident, CH_START + 4)));
printk(" regs[2] = 0x%x/0x%x\n", regs[2], inl(TRID_REG(trident, CH_START + 8)));
printk(" regs[3] = 0x%x/0x%x\n", regs[3], inl(TRID_REG(trident, CH_START + 12)));
printk(" regs[4] = 0x%x/0x%x\n", regs[4], inl(TRID_REG(trident, CH_START + 16)));
#endif
}
EXPORT_SYMBOL(snd_trident_write_voice_regs);
/*---------------------------------------------------------------------------
snd_trident_write_cso_reg
Description: This routine will write the new CSO offset
register to hardware.
Parameters: trident - pointer to target device class for 4DWave.
voice - synthesizer voice structure
CSO - new CSO value
---------------------------------------------------------------------------*/
static void snd_trident_write_cso_reg(struct snd_trident * trident,
struct snd_trident_voice * voice,
unsigned int CSO)
{
voice->CSO = CSO;
outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
if (trident->device != TRIDENT_DEVICE_ID_NX) {
outw(voice->CSO, TRID_REG(trident, CH_DX_CSO_ALPHA_FMS) + 2);
} else {
outl((voice->Delta << 24) |
(voice->CSO & 0x00ffffff), TRID_REG(trident, CH_NX_DELTA_CSO));
}
}
/*---------------------------------------------------------------------------
snd_trident_write_eso_reg
Description: This routine will write the new ESO offset
register to hardware.
Parameters: trident - pointer to target device class for 4DWave.
voice - synthesizer voice structure
ESO - new ESO value
---------------------------------------------------------------------------*/
static void snd_trident_write_eso_reg(struct snd_trident * trident,
struct snd_trident_voice * voice,
unsigned int ESO)
{
voice->ESO = ESO;
outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
if (trident->device != TRIDENT_DEVICE_ID_NX) {
outw(voice->ESO, TRID_REG(trident, CH_DX_ESO_DELTA) + 2);
} else {
outl(((voice->Delta << 16) & 0xff000000) | (voice->ESO & 0x00ffffff),
TRID_REG(trident, CH_NX_DELTA_ESO));
}
}
/*---------------------------------------------------------------------------
snd_trident_write_vol_reg
Description: This routine will write the new voice volume
register to hardware.
Parameters: trident - pointer to target device class for 4DWave.
voice - synthesizer voice structure
Vol - new voice volume
---------------------------------------------------------------------------*/
static void snd_trident_write_vol_reg(struct snd_trident * trident,
struct snd_trident_voice * voice,
unsigned int Vol)
{
voice->Vol = Vol;
outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
switch (trident->device) {
case TRIDENT_DEVICE_ID_DX:
case TRIDENT_DEVICE_ID_NX:
outb(voice->Vol >> 2, TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC + 2));
break;
case TRIDENT_DEVICE_ID_SI7018:
// printk("voice->Vol = 0x%x\n", voice->Vol);
outw((voice->CTRL << 12) | voice->Vol,
TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC));
break;
}
}
/*---------------------------------------------------------------------------
snd_trident_write_pan_reg
Description: This routine will write the new voice pan
register to hardware.
Parameters: trident - pointer to target device class for 4DWave.
voice - synthesizer voice structure
Pan - new pan value
---------------------------------------------------------------------------*/
static void snd_trident_write_pan_reg(struct snd_trident * trident,
struct snd_trident_voice * voice,
unsigned int Pan)
{
voice->Pan = Pan;
outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
outb(((voice->GVSel & 0x01) << 7) | (voice->Pan & 0x7f),
TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC + 3));
}
/*---------------------------------------------------------------------------
snd_trident_write_rvol_reg
Description: This routine will write the new reverb volume
register to hardware.
Parameters: trident - pointer to target device class for 4DWave.
voice - synthesizer voice structure
RVol - new reverb volume
---------------------------------------------------------------------------*/
static void snd_trident_write_rvol_reg(struct snd_trident * trident,
struct snd_trident_voice * voice,
unsigned int RVol)
{
voice->RVol = RVol;
outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
outw(((voice->FMC & 0x0003) << 14) | ((voice->RVol & 0x007f) << 7) |
(voice->CVol & 0x007f),
TRID_REG(trident, trident->device == TRIDENT_DEVICE_ID_NX ?
CH_NX_ALPHA_FMS_FMC_RVOL_CVOL : CH_DX_FMC_RVOL_CVOL));
}
/*---------------------------------------------------------------------------
snd_trident_write_cvol_reg
Description: This routine will write the new chorus volume
register to hardware.
Parameters: trident - pointer to target device class for 4DWave.
voice - synthesizer voice structure
CVol - new chorus volume
---------------------------------------------------------------------------*/
static void snd_trident_write_cvol_reg(struct snd_trident * trident,
struct snd_trident_voice * voice,
unsigned int CVol)
{
voice->CVol = CVol;
outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
outw(((voice->FMC & 0x0003) << 14) | ((voice->RVol & 0x007f) << 7) |
(voice->CVol & 0x007f),
TRID_REG(trident, trident->device == TRIDENT_DEVICE_ID_NX ?
CH_NX_ALPHA_FMS_FMC_RVOL_CVOL : CH_DX_FMC_RVOL_CVOL));
}
/*---------------------------------------------------------------------------
snd_trident_convert_rate
Description: This routine converts rate in HZ to hardware delta value.
Parameters: trident - pointer to target device class for 4DWave.
rate - Real or Virtual channel number.
Returns: Delta value.
---------------------------------------------------------------------------*/
static unsigned int snd_trident_convert_rate(unsigned int rate)
{
unsigned int delta;
// We special case 44100 and 8000 since rounding with the equation
// does not give us an accurate enough value. For 11025 and 22050
// the equation gives us the best answer. All other frequencies will
// also use the equation. JDW
if (rate == 44100)
delta = 0xeb3;
else if (rate == 8000)
delta = 0x2ab;
else if (rate == 48000)
delta = 0x1000;
else
delta = (((rate << 12) + 24000) / 48000) & 0x0000ffff;
return delta;
}
/*---------------------------------------------------------------------------
snd_trident_convert_adc_rate
Description: This routine converts rate in HZ to hardware delta value.
Parameters: trident - pointer to target device class for 4DWave.
rate - Real or Virtual channel number.
Returns: Delta value.
---------------------------------------------------------------------------*/
static unsigned int snd_trident_convert_adc_rate(unsigned int rate)
{
unsigned int delta;
// We special case 44100 and 8000 since rounding with the equation
// does not give us an accurate enough value. For 11025 and 22050
// the equation gives us the best answer. All other frequencies will
// also use the equation. JDW
if (rate == 44100)
delta = 0x116a;
else if (rate == 8000)
delta = 0x6000;
else if (rate == 48000)
delta = 0x1000;
else
delta = ((48000 << 12) / rate) & 0x0000ffff;
return delta;
}
/*---------------------------------------------------------------------------
snd_trident_spurious_threshold
Description: This routine converts rate in HZ to spurious threshold.
Parameters: trident - pointer to target device class for 4DWave.
rate - Real or Virtual channel number.
Returns: Delta value.
---------------------------------------------------------------------------*/
static unsigned int snd_trident_spurious_threshold(unsigned int rate,
unsigned int period_size)
{
unsigned int res = (rate * period_size) / 48000;
if (res < 64)
res = res / 2;
else
res -= 32;
return res;
}
/*---------------------------------------------------------------------------
snd_trident_control_mode
Description: This routine returns a control mode for a PCM channel.
Parameters: trident - pointer to target device class for 4DWave.
substream - PCM substream
Returns: Control value.
---------------------------------------------------------------------------*/
static unsigned int snd_trident_control_mode(struct snd_pcm_substream *substream)
{
unsigned int CTRL;
struct snd_pcm_runtime *runtime = substream->runtime;
/* set ctrl mode
CTRL default: 8-bit (unsigned) mono, loop mode enabled
*/
CTRL = 0x00000001;
if (snd_pcm_format_width(runtime->format) == 16)
CTRL |= 0x00000008; // 16-bit data
if (snd_pcm_format_signed(runtime->format))
CTRL |= 0x00000002; // signed data
if (runtime->channels > 1)
CTRL |= 0x00000004; // stereo data
return CTRL;
}
/*
* PCM part
*/
/*---------------------------------------------------------------------------
snd_trident_ioctl
Description: Device I/O control handler for playback/capture parameters.
Parameters: substream - PCM substream class
cmd - what ioctl message to process
arg - additional message infoarg
Returns: Error status
---------------------------------------------------------------------------*/
static int snd_trident_ioctl(struct snd_pcm_substream *substream,
unsigned int cmd,
void *arg)
{
/* FIXME: it seems that with small periods the behaviour of
trident hardware is unpredictable and interrupt generator
is broken */
return snd_pcm_lib_ioctl(substream, cmd, arg);
}
/*---------------------------------------------------------------------------
snd_trident_allocate_pcm_mem
Description: Allocate PCM ring buffer for given substream
Parameters: substream - PCM substream class
hw_params - hardware parameters
Returns: Error status
---------------------------------------------------------------------------*/
static int snd_trident_allocate_pcm_mem(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_trident_voice *voice = runtime->private_data;
int err;
if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
return err;
if (trident->tlb.entries) {
if (err > 0) { /* change */
if (voice->memblk)
snd_trident_free_pages(trident, voice->memblk);
voice->memblk = snd_trident_alloc_pages(trident, substream);
if (voice->memblk == NULL)
return -ENOMEM;
}
}
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_allocate_evoice
Description: Allocate extra voice as interrupt generator
Parameters: substream - PCM substream class
hw_params - hardware parameters
Returns: Error status
---------------------------------------------------------------------------*/
static int snd_trident_allocate_evoice(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_trident_voice *voice = runtime->private_data;
struct snd_trident_voice *evoice = voice->extra;
/* voice management */
if (params_buffer_size(hw_params) / 2 != params_period_size(hw_params)) {
if (evoice == NULL) {
evoice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
if (evoice == NULL)
return -ENOMEM;
voice->extra = evoice;
evoice->substream = substream;
}
} else {
if (evoice != NULL) {
snd_trident_free_voice(trident, evoice);
voice->extra = evoice = NULL;
}
}
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_hw_params
Description: Set the hardware parameters for the playback device.
Parameters: substream - PCM substream class
hw_params - hardware parameters
Returns: Error status
---------------------------------------------------------------------------*/
static int snd_trident_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
int err;
err = snd_trident_allocate_pcm_mem(substream, hw_params);
if (err >= 0)
err = snd_trident_allocate_evoice(substream, hw_params);
return err;
}
/*---------------------------------------------------------------------------
snd_trident_playback_hw_free
Description: Release the hardware resources for the playback device.
Parameters: substream - PCM substream class
Returns: Error status
---------------------------------------------------------------------------*/
static int snd_trident_hw_free(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_trident_voice *voice = runtime->private_data;
struct snd_trident_voice *evoice = voice ? voice->extra : NULL;
if (trident->tlb.entries) {
if (voice && voice->memblk) {
snd_trident_free_pages(trident, voice->memblk);
voice->memblk = NULL;
}
}
snd_pcm_lib_free_pages(substream);
if (evoice != NULL) {
snd_trident_free_voice(trident, evoice);
voice->extra = NULL;
}
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_playback_prepare
Description: Prepare playback device for playback.
Parameters: substream - PCM substream class
Returns: Error status
---------------------------------------------------------------------------*/
static int snd_trident_playback_prepare(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_trident_voice *voice = runtime->private_data;
struct snd_trident_voice *evoice = voice->extra;
struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[substream->number];
spin_lock_irq(&trident->reg_lock);
/* set delta (rate) value */
voice->Delta = snd_trident_convert_rate(runtime->rate);
voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
/* set Loop Begin Address */
if (voice->memblk)
voice->LBA = voice->memblk->offset;
else
voice->LBA = runtime->dma_addr;
voice->CSO = 0;
voice->ESO = runtime->buffer_size - 1; /* in samples */
voice->CTRL = snd_trident_control_mode(substream);
voice->FMC = 3;
voice->GVSel = 1;
voice->EC = 0;
voice->Alpha = 0;
voice->FMS = 0;
voice->Vol = mix->vol;
voice->RVol = mix->rvol;
voice->CVol = mix->cvol;
voice->Pan = mix->pan;
voice->Attribute = 0;
#if 0
voice->Attribute = (1<<(30-16))|(2<<(26-16))|
(0<<(24-16))|(0x1f<<(19-16));
#else
voice->Attribute = 0;
#endif
snd_trident_write_voice_regs(trident, voice);
if (evoice != NULL) {
evoice->Delta = voice->Delta;
evoice->spurious_threshold = voice->spurious_threshold;
evoice->LBA = voice->LBA;
evoice->CSO = 0;
evoice->ESO = (runtime->period_size * 2) + 4 - 1; /* in samples */
evoice->CTRL = voice->CTRL;
evoice->FMC = 3;
evoice->GVSel = trident->device == TRIDENT_DEVICE_ID_SI7018 ? 0 : 1;
evoice->EC = 0;
evoice->Alpha = 0;
evoice->FMS = 0;
evoice->Vol = 0x3ff; /* mute */
evoice->RVol = evoice->CVol = 0x7f; /* mute */
evoice->Pan = 0x7f; /* mute */
#if 0
evoice->Attribute = (1<<(30-16))|(2<<(26-16))|
(0<<(24-16))|(0x1f<<(19-16));
#else
evoice->Attribute = 0;
#endif
snd_trident_write_voice_regs(trident, evoice);
evoice->isync2 = 1;
evoice->isync_mark = runtime->period_size;
evoice->ESO = (runtime->period_size * 2) - 1;
}
spin_unlock_irq(&trident->reg_lock);
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_capture_hw_params
Description: Set the hardware parameters for the capture device.
Parameters: substream - PCM substream class
hw_params - hardware parameters
Returns: Error status
---------------------------------------------------------------------------*/
static int snd_trident_capture_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
return snd_trident_allocate_pcm_mem(substream, hw_params);
}
/*---------------------------------------------------------------------------
snd_trident_capture_prepare
Description: Prepare capture device for playback.
Parameters: substream - PCM substream class
Returns: Error status
---------------------------------------------------------------------------*/
static int snd_trident_capture_prepare(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_trident_voice *voice = runtime->private_data;
unsigned int val, ESO_bytes;
spin_lock_irq(&trident->reg_lock);
// Initilize the channel and set channel Mode
outb(0, TRID_REG(trident, LEGACY_DMAR15));
// Set DMA channel operation mode register
outb(0x54, TRID_REG(trident, LEGACY_DMAR11));
// Set channel buffer Address, DMAR0 expects contiguous PCI memory area
voice->LBA = runtime->dma_addr;
outl(voice->LBA, TRID_REG(trident, LEGACY_DMAR0));
if (voice->memblk)
voice->LBA = voice->memblk->offset;
// set ESO
ESO_bytes = snd_pcm_lib_buffer_bytes(substream) - 1;
outb((ESO_bytes & 0x00ff0000) >> 16, TRID_REG(trident, LEGACY_DMAR6));
outw((ESO_bytes & 0x0000ffff), TRID_REG(trident, LEGACY_DMAR4));
ESO_bytes++;
// Set channel sample rate, 4.12 format
val = (((unsigned int) 48000L << 12) + (runtime->rate/2)) / runtime->rate;
outw(val, TRID_REG(trident, T4D_SBDELTA_DELTA_R));
// Set channel interrupt blk length
if (snd_pcm_format_width(runtime->format) == 16) {
val = (unsigned short) ((ESO_bytes >> 1) - 1);
} else {
val = (unsigned short) (ESO_bytes - 1);
}
outl((val << 16) | val, TRID_REG(trident, T4D_SBBL_SBCL));
// Right now, set format and start to run captureing,
// continuous run loop enable.
trident->bDMAStart = 0x19; // 0001 1001b
if (snd_pcm_format_width(runtime->format) == 16)
trident->bDMAStart |= 0x80;
if (snd_pcm_format_signed(runtime->format))
trident->bDMAStart |= 0x20;
if (runtime->channels > 1)
trident->bDMAStart |= 0x40;
// Prepare capture intr channel
voice->Delta = snd_trident_convert_rate(runtime->rate);
voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
voice->isync = 1;
voice->isync_mark = runtime->period_size;
voice->isync_max = runtime->buffer_size;
// Set voice parameters
voice->CSO = 0;
voice->ESO = voice->isync_ESO = (runtime->period_size * 2) + 6 - 1;
voice->CTRL = snd_trident_control_mode(substream);
voice->FMC = 3;
voice->RVol = 0x7f;
voice->CVol = 0x7f;
voice->GVSel = 1;
voice->Pan = 0x7f; /* mute */
voice->Vol = 0x3ff; /* mute */
voice->EC = 0;
voice->Alpha = 0;
voice->FMS = 0;
voice->Attribute = 0;
snd_trident_write_voice_regs(trident, voice);
spin_unlock_irq(&trident->reg_lock);
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_si7018_capture_hw_params
Description: Set the hardware parameters for the capture device.
Parameters: substream - PCM substream class
hw_params - hardware parameters
Returns: Error status
---------------------------------------------------------------------------*/
static int snd_trident_si7018_capture_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
int err;
if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
return err;
return snd_trident_allocate_evoice(substream, hw_params);
}
/*---------------------------------------------------------------------------
snd_trident_si7018_capture_hw_free
Description: Release the hardware resources for the capture device.
Parameters: substream - PCM substream class
Returns: Error status
---------------------------------------------------------------------------*/
static int snd_trident_si7018_capture_hw_free(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_trident_voice *voice = runtime->private_data;
struct snd_trident_voice *evoice = voice ? voice->extra : NULL;
snd_pcm_lib_free_pages(substream);
if (evoice != NULL) {
snd_trident_free_voice(trident, evoice);
voice->extra = NULL;
}
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_si7018_capture_prepare
Description: Prepare capture device for playback.
Parameters: substream - PCM substream class
Returns: Error status
---------------------------------------------------------------------------*/
static int snd_trident_si7018_capture_prepare(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_trident_voice *voice = runtime->private_data;
struct snd_trident_voice *evoice = voice->extra;
spin_lock_irq(&trident->reg_lock);
voice->LBA = runtime->dma_addr;
voice->Delta = snd_trident_convert_adc_rate(runtime->rate);
voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
// Set voice parameters
voice->CSO = 0;
voice->ESO = runtime->buffer_size - 1; /* in samples */
voice->CTRL = snd_trident_control_mode(substream);
voice->FMC = 0;
voice->RVol = 0;
voice->CVol = 0;
voice->GVSel = 1;
voice->Pan = T4D_DEFAULT_PCM_PAN;
voice->Vol = 0;
voice->EC = 0;
voice->Alpha = 0;
voice->FMS = 0;
voice->Attribute = (2 << (30-16)) |
(2 << (26-16)) |
(2 << (24-16)) |
(1 << (23-16));
snd_trident_write_voice_regs(trident, voice);
if (evoice != NULL) {
evoice->Delta = snd_trident_convert_rate(runtime->rate);
evoice->spurious_threshold = voice->spurious_threshold;
evoice->LBA = voice->LBA;
evoice->CSO = 0;
evoice->ESO = (runtime->period_size * 2) + 20 - 1; /* in samples, 20 means correction */
evoice->CTRL = voice->CTRL;
evoice->FMC = 3;
evoice->GVSel = 0;
evoice->EC = 0;
evoice->Alpha = 0;
evoice->FMS = 0;
evoice->Vol = 0x3ff; /* mute */
evoice->RVol = evoice->CVol = 0x7f; /* mute */
evoice->Pan = 0x7f; /* mute */
evoice->Attribute = 0;
snd_trident_write_voice_regs(trident, evoice);
evoice->isync2 = 1;
evoice->isync_mark = runtime->period_size;
evoice->ESO = (runtime->period_size * 2) - 1;
}
spin_unlock_irq(&trident->reg_lock);
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_foldback_prepare
Description: Prepare foldback capture device for playback.
Parameters: substream - PCM substream class
Returns: Error status
---------------------------------------------------------------------------*/
static int snd_trident_foldback_prepare(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_trident_voice *voice = runtime->private_data;
struct snd_trident_voice *evoice = voice->extra;
spin_lock_irq(&trident->reg_lock);
/* Set channel buffer Address */
if (voice->memblk)
voice->LBA = voice->memblk->offset;
else
voice->LBA = runtime->dma_addr;
/* set target ESO for channel */
voice->ESO = runtime->buffer_size - 1; /* in samples */
/* set sample rate */
voice->Delta = 0x1000;
voice->spurious_threshold = snd_trident_spurious_threshold(48000, runtime->period_size);
voice->CSO = 0;
voice->CTRL = snd_trident_control_mode(substream);
voice->FMC = 3;
voice->RVol = 0x7f;
voice->CVol = 0x7f;
voice->GVSel = 1;
voice->Pan = 0x7f; /* mute */
voice->Vol = 0x3ff; /* mute */
voice->EC = 0;
voice->Alpha = 0;
voice->FMS = 0;
voice->Attribute = 0;
/* set up capture channel */
outb(((voice->number & 0x3f) | 0x80), TRID_REG(trident, T4D_RCI + voice->foldback_chan));
snd_trident_write_voice_regs(trident, voice);
if (evoice != NULL) {
evoice->Delta = voice->Delta;
evoice->spurious_threshold = voice->spurious_threshold;
evoice->LBA = voice->LBA;
evoice->CSO = 0;
evoice->ESO = (runtime->period_size * 2) + 4 - 1; /* in samples */
evoice->CTRL = voice->CTRL;
evoice->FMC = 3;
evoice->GVSel = trident->device == TRIDENT_DEVICE_ID_SI7018 ? 0 : 1;
evoice->EC = 0;
evoice->Alpha = 0;
evoice->FMS = 0;
evoice->Vol = 0x3ff; /* mute */
evoice->RVol = evoice->CVol = 0x7f; /* mute */
evoice->Pan = 0x7f; /* mute */
evoice->Attribute = 0;
snd_trident_write_voice_regs(trident, evoice);
evoice->isync2 = 1;
evoice->isync_mark = runtime->period_size;
evoice->ESO = (runtime->period_size * 2) - 1;
}
spin_unlock_irq(&trident->reg_lock);
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_spdif_hw_params
Description: Set the hardware parameters for the spdif device.
Parameters: substream - PCM substream class
hw_params - hardware parameters
Returns: Error status
---------------------------------------------------------------------------*/
static int snd_trident_spdif_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
unsigned int old_bits = 0, change = 0;
int err;
err = snd_trident_allocate_pcm_mem(substream, hw_params);
if (err < 0)
return err;
if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
err = snd_trident_allocate_evoice(substream, hw_params);
if (err < 0)
return err;
}
/* prepare SPDIF channel */
spin_lock_irq(&trident->reg_lock);
old_bits = trident->spdif_pcm_bits;
if (old_bits & IEC958_AES0_PROFESSIONAL)
trident->spdif_pcm_bits &= ~IEC958_AES0_PRO_FS;
else
trident->spdif_pcm_bits &= ~(IEC958_AES3_CON_FS << 24);
if (params_rate(hw_params) >= 48000) {
trident->spdif_pcm_ctrl = 0x3c; // 48000 Hz
trident->spdif_pcm_bits |=
trident->spdif_bits & IEC958_AES0_PROFESSIONAL ?
IEC958_AES0_PRO_FS_48000 :
(IEC958_AES3_CON_FS_48000 << 24);
}
else if (params_rate(hw_params) >= 44100) {
trident->spdif_pcm_ctrl = 0x3e; // 44100 Hz
trident->spdif_pcm_bits |=
trident->spdif_bits & IEC958_AES0_PROFESSIONAL ?
IEC958_AES0_PRO_FS_44100 :
(IEC958_AES3_CON_FS_44100 << 24);
}
else {
trident->spdif_pcm_ctrl = 0x3d; // 32000 Hz
trident->spdif_pcm_bits |=
trident->spdif_bits & IEC958_AES0_PROFESSIONAL ?
IEC958_AES0_PRO_FS_32000 :
(IEC958_AES3_CON_FS_32000 << 24);
}
change = old_bits != trident->spdif_pcm_bits;
spin_unlock_irq(&trident->reg_lock);
if (change)
snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE, &trident->spdif_pcm_ctl->id);
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_spdif_prepare
Description: Prepare SPDIF device for playback.
Parameters: substream - PCM substream class
Returns: Error status
---------------------------------------------------------------------------*/
static int snd_trident_spdif_prepare(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_trident_voice *voice = runtime->private_data;
struct snd_trident_voice *evoice = voice->extra;
struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[substream->number];
unsigned int RESO, LBAO;
unsigned int temp;
spin_lock_irq(&trident->reg_lock);
if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
/* set delta (rate) value */
voice->Delta = snd_trident_convert_rate(runtime->rate);
voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
/* set Loop Back Address */
LBAO = runtime->dma_addr;
if (voice->memblk)
voice->LBA = voice->memblk->offset;
else
voice->LBA = LBAO;
voice->isync = 1;
voice->isync3 = 1;
voice->isync_mark = runtime->period_size;
voice->isync_max = runtime->buffer_size;
/* set target ESO for channel */
RESO = runtime->buffer_size - 1;
voice->ESO = voice->isync_ESO = (runtime->period_size * 2) + 6 - 1;
/* set ctrl mode */
voice->CTRL = snd_trident_control_mode(substream);
voice->FMC = 3;
voice->RVol = 0x7f;
voice->CVol = 0x7f;
voice->GVSel = 1;
voice->Pan = 0x7f;
voice->Vol = 0x3ff;
voice->EC = 0;
voice->CSO = 0;
voice->Alpha = 0;
voice->FMS = 0;
voice->Attribute = 0;
/* prepare surrogate IRQ channel */
snd_trident_write_voice_regs(trident, voice);
outw((RESO & 0xffff), TRID_REG(trident, NX_SPESO));
outb((RESO >> 16), TRID_REG(trident, NX_SPESO + 2));
outl((LBAO & 0xfffffffc), TRID_REG(trident, NX_SPLBA));
outw((voice->CSO & 0xffff), TRID_REG(trident, NX_SPCTRL_SPCSO));
outb((voice->CSO >> 16), TRID_REG(trident, NX_SPCTRL_SPCSO + 2));
/* set SPDIF setting */
outb(trident->spdif_pcm_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS));
} else { /* SiS */
/* set delta (rate) value */
voice->Delta = 0x800;
voice->spurious_threshold = snd_trident_spurious_threshold(48000, runtime->period_size);
/* set Loop Begin Address */
if (voice->memblk)
voice->LBA = voice->memblk->offset;
else
voice->LBA = runtime->dma_addr;
voice->CSO = 0;
voice->ESO = runtime->buffer_size - 1; /* in samples */
voice->CTRL = snd_trident_control_mode(substream);
voice->FMC = 3;
voice->GVSel = 1;
voice->EC = 0;
voice->Alpha = 0;
voice->FMS = 0;
voice->Vol = mix->vol;
voice->RVol = mix->rvol;
voice->CVol = mix->cvol;
voice->Pan = mix->pan;
voice->Attribute = (1<<(30-16))|(7<<(26-16))|
(0<<(24-16))|(0<<(19-16));
snd_trident_write_voice_regs(trident, voice);
if (evoice != NULL) {
evoice->Delta = voice->Delta;
evoice->spurious_threshold = voice->spurious_threshold;
evoice->LBA = voice->LBA;
evoice->CSO = 0;
evoice->ESO = (runtime->period_size * 2) + 4 - 1; /* in samples */
evoice->CTRL = voice->CTRL;
evoice->FMC = 3;
evoice->GVSel = trident->device == TRIDENT_DEVICE_ID_SI7018 ? 0 : 1;
evoice->EC = 0;
evoice->Alpha = 0;
evoice->FMS = 0;
evoice->Vol = 0x3ff; /* mute */
evoice->RVol = evoice->CVol = 0x7f; /* mute */
evoice->Pan = 0x7f; /* mute */
evoice->Attribute = 0;
snd_trident_write_voice_regs(trident, evoice);
evoice->isync2 = 1;
evoice->isync_mark = runtime->period_size;
evoice->ESO = (runtime->period_size * 2) - 1;
}
outl(trident->spdif_pcm_bits, TRID_REG(trident, SI_SPDIF_CS));
temp = inl(TRID_REG(trident, T4D_LFO_GC_CIR));
temp &= ~(1<<19);
outl(temp, TRID_REG(trident, T4D_LFO_GC_CIR));
temp = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL));
temp |= SPDIF_EN;
outl(temp, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
}
spin_unlock_irq(&trident->reg_lock);
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_trigger
Description: Start/stop devices
Parameters: substream - PCM substream class
cmd - trigger command (STOP, GO)
Returns: Error status
---------------------------------------------------------------------------*/
static int snd_trident_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_pcm_substream *s;
unsigned int what, whati, capture_flag, spdif_flag;
struct snd_trident_voice *voice, *evoice;
unsigned int val, go;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
go = 1;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
go = 0;
break;
default:
return -EINVAL;
}
what = whati = capture_flag = spdif_flag = 0;
spin_lock(&trident->reg_lock);
val = inl(TRID_REG(trident, T4D_STIMER)) & 0x00ffffff;
snd_pcm_group_for_each_entry(s, substream) {
if ((struct snd_trident *) snd_pcm_substream_chip(s) == trident) {
voice = s->runtime->private_data;
evoice = voice->extra;
what |= 1 << (voice->number & 0x1f);
if (evoice == NULL) {
whati |= 1 << (voice->number & 0x1f);
} else {
what |= 1 << (evoice->number & 0x1f);
whati |= 1 << (evoice->number & 0x1f);
if (go)
evoice->stimer = val;
}
if (go) {
voice->running = 1;
voice->stimer = val;
} else {
voice->running = 0;
}
snd_pcm_trigger_done(s, substream);
if (voice->capture)
capture_flag = 1;
if (voice->spdif)
spdif_flag = 1;
}
}
if (spdif_flag) {
if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS));
val = trident->spdif_pcm_ctrl;
if (!go)
val &= ~(0x28);
outb(val, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
} else {
outl(trident->spdif_pcm_bits, TRID_REG(trident, SI_SPDIF_CS));
val = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) | SPDIF_EN;
outl(val, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
}
}
if (!go)
outl(what, TRID_REG(trident, T4D_STOP_B));
val = inl(TRID_REG(trident, T4D_AINTEN_B));
if (go) {
val |= whati;
} else {
val &= ~whati;
}
outl(val, TRID_REG(trident, T4D_AINTEN_B));
if (go) {
outl(what, TRID_REG(trident, T4D_START_B));
if (capture_flag && trident->device != TRIDENT_DEVICE_ID_SI7018)
outb(trident->bDMAStart, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD));
} else {
if (capture_flag && trident->device != TRIDENT_DEVICE_ID_SI7018)
outb(0x00, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD));
}
spin_unlock(&trident->reg_lock);
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_playback_pointer
Description: This routine return the playback position
Parameters: substream - PCM substream class
Returns: position of buffer
---------------------------------------------------------------------------*/
static snd_pcm_uframes_t snd_trident_playback_pointer(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_trident_voice *voice = runtime->private_data;
unsigned int cso;
if (!voice->running)
return 0;
spin_lock(&trident->reg_lock);
outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
if (trident->device != TRIDENT_DEVICE_ID_NX) {
cso = inw(TRID_REG(trident, CH_DX_CSO_ALPHA_FMS + 2));
} else { // ID_4DWAVE_NX
cso = (unsigned int) inl(TRID_REG(trident, CH_NX_DELTA_CSO)) & 0x00ffffff;
}
spin_unlock(&trident->reg_lock);
if (cso >= runtime->buffer_size)
cso = 0;
return cso;
}
/*---------------------------------------------------------------------------
snd_trident_capture_pointer
Description: This routine return the capture position
Parameters: pcm1 - PCM device class
Returns: position of buffer
---------------------------------------------------------------------------*/
static snd_pcm_uframes_t snd_trident_capture_pointer(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_trident_voice *voice = runtime->private_data;
unsigned int result;
if (!voice->running)
return 0;
result = inw(TRID_REG(trident, T4D_SBBL_SBCL));
if (runtime->channels > 1)
result >>= 1;
if (result > 0)
result = runtime->buffer_size - result;
return result;
}
/*---------------------------------------------------------------------------
snd_trident_spdif_pointer
Description: This routine return the SPDIF playback position
Parameters: substream - PCM substream class
Returns: position of buffer
---------------------------------------------------------------------------*/
static snd_pcm_uframes_t snd_trident_spdif_pointer(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_trident_voice *voice = runtime->private_data;
unsigned int result;
if (!voice->running)
return 0;
result = inl(TRID_REG(trident, NX_SPCTRL_SPCSO)) & 0x00ffffff;
return result;
}
/*
* Playback support device description
*/
static struct snd_pcm_hardware snd_trident_playback =
{
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */),
.formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U16_LE),
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
.rate_min = 4000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = (256*1024),
.period_bytes_min = 64,
.period_bytes_max = (256*1024),
.periods_min = 1,
.periods_max = 1024,
.fifo_size = 0,
};
/*
* Capture support device description
*/
static struct snd_pcm_hardware snd_trident_capture =
{
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */),
.formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U16_LE),
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
.rate_min = 4000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = (128*1024),
.period_bytes_min = 64,
.period_bytes_max = (128*1024),
.periods_min = 1,
.periods_max = 1024,
.fifo_size = 0,
};
/*
* Foldback capture support device description
*/
static struct snd_pcm_hardware snd_trident_foldback =
{
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = (128*1024),
.period_bytes_min = 64,
.period_bytes_max = (128*1024),
.periods_min = 1,
.periods_max = 1024,
.fifo_size = 0,
};
/*
* SPDIF playback support device description
*/
static struct snd_pcm_hardware snd_trident_spdif =
{
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000),
.rate_min = 32000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = (128*1024),
.period_bytes_min = 64,
.period_bytes_max = (128*1024),
.periods_min = 1,
.periods_max = 1024,
.fifo_size = 0,
};
static struct snd_pcm_hardware snd_trident_spdif_7018 =
{
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = (128*1024),
.period_bytes_min = 64,
.period_bytes_max = (128*1024),
.periods_min = 1,
.periods_max = 1024,
.fifo_size = 0,
};
static void snd_trident_pcm_free_substream(struct snd_pcm_runtime *runtime)
{
struct snd_trident_voice *voice = runtime->private_data;
struct snd_trident *trident;
if (voice) {
trident = voice->trident;
snd_trident_free_voice(trident, voice);
}
}
static int snd_trident_playback_open(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_trident_voice *voice;
voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
if (voice == NULL)
return -EAGAIN;
snd_trident_pcm_mixer_build(trident, voice, substream);
voice->substream = substream;
runtime->private_data = voice;
runtime->private_free = snd_trident_pcm_free_substream;
runtime->hw = snd_trident_playback;
snd_pcm_set_sync(substream);
snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_playback_close
Description: This routine will close the 4DWave playback device. For now
we will simply free the dma transfer buffer.
Parameters: substream - PCM substream class
---------------------------------------------------------------------------*/
static int snd_trident_playback_close(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_trident_voice *voice = runtime->private_data;
snd_trident_pcm_mixer_free(trident, voice, substream);
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_spdif_open
Description: This routine will open the 4DWave SPDIF device.
Parameters: substream - PCM substream class
Returns: status - success or failure flag
---------------------------------------------------------------------------*/
static int snd_trident_spdif_open(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_trident_voice *voice;
struct snd_pcm_runtime *runtime = substream->runtime;
voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
if (voice == NULL)
return -EAGAIN;
voice->spdif = 1;
voice->substream = substream;
spin_lock_irq(&trident->reg_lock);
trident->spdif_pcm_bits = trident->spdif_bits;
spin_unlock_irq(&trident->reg_lock);
runtime->private_data = voice;
runtime->private_free = snd_trident_pcm_free_substream;
if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
runtime->hw = snd_trident_spdif;
} else {
runtime->hw = snd_trident_spdif_7018;
}
trident->spdif_pcm_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE |
SNDRV_CTL_EVENT_MASK_INFO, &trident->spdif_pcm_ctl->id);
snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_spdif_close
Description: This routine will close the 4DWave SPDIF device.
Parameters: substream - PCM substream class
---------------------------------------------------------------------------*/
static int snd_trident_spdif_close(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
unsigned int temp;
spin_lock_irq(&trident->reg_lock);
// restore default SPDIF setting
if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
} else {
outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
temp = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL));
if (trident->spdif_ctrl) {
temp |= SPDIF_EN;
} else {
temp &= ~SPDIF_EN;
}
outl(temp, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
}
spin_unlock_irq(&trident->reg_lock);
trident->spdif_pcm_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE |
SNDRV_CTL_EVENT_MASK_INFO, &trident->spdif_pcm_ctl->id);
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_capture_open
Description: This routine will open the 4DWave capture device.
Parameters: substream - PCM substream class
Returns: status - success or failure flag
---------------------------------------------------------------------------*/
static int snd_trident_capture_open(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_trident_voice *voice;
struct snd_pcm_runtime *runtime = substream->runtime;
voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
if (voice == NULL)
return -EAGAIN;
voice->capture = 1;
voice->substream = substream;
runtime->private_data = voice;
runtime->private_free = snd_trident_pcm_free_substream;
runtime->hw = snd_trident_capture;
snd_pcm_set_sync(substream);
snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_capture_close
Description: This routine will close the 4DWave capture device. For now
we will simply free the dma transfer buffer.
Parameters: substream - PCM substream class
---------------------------------------------------------------------------*/
static int snd_trident_capture_close(struct snd_pcm_substream *substream)
{
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_foldback_open
Description: This routine will open the 4DWave foldback capture device.
Parameters: substream - PCM substream class
Returns: status - success or failure flag
---------------------------------------------------------------------------*/
static int snd_trident_foldback_open(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_trident_voice *voice;
struct snd_pcm_runtime *runtime = substream->runtime;
voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
if (voice == NULL)
return -EAGAIN;
voice->foldback_chan = substream->number;
voice->substream = substream;
runtime->private_data = voice;
runtime->private_free = snd_trident_pcm_free_substream;
runtime->hw = snd_trident_foldback;
snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_foldback_close
Description: This routine will close the 4DWave foldback capture device.
For now we will simply free the dma transfer buffer.
Parameters: substream - PCM substream class
---------------------------------------------------------------------------*/
static int snd_trident_foldback_close(struct snd_pcm_substream *substream)
{
struct snd_trident *trident = snd_pcm_substream_chip(substream);
struct snd_trident_voice *voice;
struct snd_pcm_runtime *runtime = substream->runtime;
voice = runtime->private_data;
/* stop capture channel */
spin_lock_irq(&trident->reg_lock);
outb(0x00, TRID_REG(trident, T4D_RCI + voice->foldback_chan));
spin_unlock_irq(&trident->reg_lock);
return 0;
}
/*---------------------------------------------------------------------------
PCM operations
---------------------------------------------------------------------------*/
static struct snd_pcm_ops snd_trident_playback_ops = {
.open = snd_trident_playback_open,
.close = snd_trident_playback_close,
.ioctl = snd_trident_ioctl,
.hw_params = snd_trident_hw_params,
.hw_free = snd_trident_hw_free,
.prepare = snd_trident_playback_prepare,
.trigger = snd_trident_trigger,
.pointer = snd_trident_playback_pointer,
};
static struct snd_pcm_ops snd_trident_nx_playback_ops = {
.open = snd_trident_playback_open,
.close = snd_trident_playback_close,
.ioctl = snd_trident_ioctl,
.hw_params = snd_trident_hw_params,
.hw_free = snd_trident_hw_free,
.prepare = snd_trident_playback_prepare,
.trigger = snd_trident_trigger,
.pointer = snd_trident_playback_pointer,
.page = snd_pcm_sgbuf_ops_page,
};
static struct snd_pcm_ops snd_trident_capture_ops = {
.open = snd_trident_capture_open,
.close = snd_trident_capture_close,
.ioctl = snd_trident_ioctl,
.hw_params = snd_trident_capture_hw_params,
.hw_free = snd_trident_hw_free,
.prepare = snd_trident_capture_prepare,
.trigger = snd_trident_trigger,
.pointer = snd_trident_capture_pointer,
};
static struct snd_pcm_ops snd_trident_si7018_capture_ops = {
.open = snd_trident_capture_open,
.close = snd_trident_capture_close,
.ioctl = snd_trident_ioctl,
.hw_params = snd_trident_si7018_capture_hw_params,
.hw_free = snd_trident_si7018_capture_hw_free,
.prepare = snd_trident_si7018_capture_prepare,
.trigger = snd_trident_trigger,
.pointer = snd_trident_playback_pointer,
};
static struct snd_pcm_ops snd_trident_foldback_ops = {
.open = snd_trident_foldback_open,
.close = snd_trident_foldback_close,
.ioctl = snd_trident_ioctl,
.hw_params = snd_trident_hw_params,
.hw_free = snd_trident_hw_free,
.prepare = snd_trident_foldback_prepare,
.trigger = snd_trident_trigger,
.pointer = snd_trident_playback_pointer,
};
static struct snd_pcm_ops snd_trident_nx_foldback_ops = {
.open = snd_trident_foldback_open,
.close = snd_trident_foldback_close,
.ioctl = snd_trident_ioctl,
.hw_params = snd_trident_hw_params,
.hw_free = snd_trident_hw_free,
.prepare = snd_trident_foldback_prepare,
.trigger = snd_trident_trigger,
.pointer = snd_trident_playback_pointer,
.page = snd_pcm_sgbuf_ops_page,
};
static struct snd_pcm_ops snd_trident_spdif_ops = {
.open = snd_trident_spdif_open,
.close = snd_trident_spdif_close,
.ioctl = snd_trident_ioctl,
.hw_params = snd_trident_spdif_hw_params,
.hw_free = snd_trident_hw_free,
.prepare = snd_trident_spdif_prepare,
.trigger = snd_trident_trigger,
.pointer = snd_trident_spdif_pointer,
};
static struct snd_pcm_ops snd_trident_spdif_7018_ops = {
.open = snd_trident_spdif_open,
.close = snd_trident_spdif_close,
.ioctl = snd_trident_ioctl,
.hw_params = snd_trident_spdif_hw_params,
.hw_free = snd_trident_hw_free,
.prepare = snd_trident_spdif_prepare,
.trigger = snd_trident_trigger,
.pointer = snd_trident_playback_pointer,
};
/*---------------------------------------------------------------------------
snd_trident_pcm
Description: This routine registers the 4DWave device for PCM support.
Parameters: trident - pointer to target device class for 4DWave.
Returns: None
---------------------------------------------------------------------------*/
int __devinit snd_trident_pcm(struct snd_trident * trident,
int device, struct snd_pcm ** rpcm)
{
struct snd_pcm *pcm;
int err;
if (rpcm)
*rpcm = NULL;
if ((err = snd_pcm_new(trident->card, "trident_dx_nx", device, trident->ChanPCM, 1, &pcm)) < 0)
return err;
pcm->private_data = trident;
if (trident->tlb.entries) {
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_nx_playback_ops);
} else {
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_playback_ops);
}
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
trident->device != TRIDENT_DEVICE_ID_SI7018 ?
&snd_trident_capture_ops :
&snd_trident_si7018_capture_ops);
pcm->info_flags = 0;
pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
strcpy(pcm->name, "Trident 4DWave");
trident->pcm = pcm;
if (trident->tlb.entries) {
struct snd_pcm_substream *substream;
for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next)
snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV_SG,
snd_dma_pci_data(trident->pci),
64*1024, 128*1024);
snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci),
64*1024, 128*1024);
} else {
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
snd_dma_pci_data(trident->pci), 64*1024, 128*1024);
}
if (rpcm)
*rpcm = pcm;
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_foldback_pcm
Description: This routine registers the 4DWave device for foldback PCM support.
Parameters: trident - pointer to target device class for 4DWave.
Returns: None
---------------------------------------------------------------------------*/
int __devinit snd_trident_foldback_pcm(struct snd_trident * trident,
int device, struct snd_pcm ** rpcm)
{
struct snd_pcm *foldback;
int err;
int num_chan = 3;
struct snd_pcm_substream *substream;
if (rpcm)
*rpcm = NULL;
if (trident->device == TRIDENT_DEVICE_ID_NX)
num_chan = 4;
if ((err = snd_pcm_new(trident->card, "trident_dx_nx", device, 0, num_chan, &foldback)) < 0)
return err;
foldback->private_data = trident;
if (trident->tlb.entries)
snd_pcm_set_ops(foldback, SNDRV_PCM_STREAM_CAPTURE, &snd_trident_nx_foldback_ops);
else
snd_pcm_set_ops(foldback, SNDRV_PCM_STREAM_CAPTURE, &snd_trident_foldback_ops);
foldback->info_flags = 0;
strcpy(foldback->name, "Trident 4DWave");
substream = foldback->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
strcpy(substream->name, "Front Mixer");
substream = substream->next;
strcpy(substream->name, "Reverb Mixer");
substream = substream->next;
strcpy(substream->name, "Chorus Mixer");
if (num_chan == 4) {
substream = substream->next;
strcpy(substream->name, "Second AC'97 ADC");
}
trident->foldback = foldback;
if (trident->tlb.entries)
snd_pcm_lib_preallocate_pages_for_all(foldback, SNDRV_DMA_TYPE_DEV_SG,
snd_dma_pci_data(trident->pci), 0, 128*1024);
else
snd_pcm_lib_preallocate_pages_for_all(foldback, SNDRV_DMA_TYPE_DEV,
snd_dma_pci_data(trident->pci), 64*1024, 128*1024);
if (rpcm)
*rpcm = foldback;
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_spdif
Description: This routine registers the 4DWave-NX device for SPDIF support.
Parameters: trident - pointer to target device class for 4DWave-NX.
Returns: None
---------------------------------------------------------------------------*/
int __devinit snd_trident_spdif_pcm(struct snd_trident * trident,
int device, struct snd_pcm ** rpcm)
{
struct snd_pcm *spdif;
int err;
if (rpcm)
*rpcm = NULL;
if ((err = snd_pcm_new(trident->card, "trident_dx_nx IEC958", device, 1, 0, &spdif)) < 0)
return err;
spdif->private_data = trident;
if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
snd_pcm_set_ops(spdif, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_spdif_ops);
} else {
snd_pcm_set_ops(spdif, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_spdif_7018_ops);
}
spdif->info_flags = 0;
strcpy(spdif->name, "Trident 4DWave IEC958");
trident->spdif = spdif;
snd_pcm_lib_preallocate_pages_for_all(spdif, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci), 64*1024, 128*1024);
if (rpcm)
*rpcm = spdif;
return 0;
}
/*
* Mixer part
*/
/*---------------------------------------------------------------------------
snd_trident_spdif_control
Description: enable/disable S/PDIF out from ac97 mixer
---------------------------------------------------------------------------*/
#define snd_trident_spdif_control_info snd_ctl_boolean_mono_info
static int snd_trident_spdif_control_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
unsigned char val;
spin_lock_irq(&trident->reg_lock);
val = trident->spdif_ctrl;
ucontrol->value.integer.value[0] = val == kcontrol->private_value;
spin_unlock_irq(&trident->reg_lock);
return 0;
}
static int snd_trident_spdif_control_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
unsigned char val;
int change;
val = ucontrol->value.integer.value[0] ? (unsigned char) kcontrol->private_value : 0x00;
spin_lock_irq(&trident->reg_lock);
/* S/PDIF C Channel bits 0-31 : 48khz, SCMS disabled */
change = trident->spdif_ctrl != val;
trident->spdif_ctrl = val;
if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
if ((inb(TRID_REG(trident, NX_SPCTRL_SPCSO + 3)) & 0x10) == 0) {
outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
}
} else {
if (trident->spdif == NULL) {
unsigned int temp;
outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
temp = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & ~SPDIF_EN;
if (val)
temp |= SPDIF_EN;
outl(temp, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
}
}
spin_unlock_irq(&trident->reg_lock);
return change;
}
static struct snd_kcontrol_new snd_trident_spdif_control __devinitdata =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH),
.info = snd_trident_spdif_control_info,
.get = snd_trident_spdif_control_get,
.put = snd_trident_spdif_control_put,
.private_value = 0x28,
};
/*---------------------------------------------------------------------------
snd_trident_spdif_default
Description: put/get the S/PDIF default settings
---------------------------------------------------------------------------*/
static int snd_trident_spdif_default_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}
static int snd_trident_spdif_default_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
spin_lock_irq(&trident->reg_lock);
ucontrol->value.iec958.status[0] = (trident->spdif_bits >> 0) & 0xff;
ucontrol->value.iec958.status[1] = (trident->spdif_bits >> 8) & 0xff;
ucontrol->value.iec958.status[2] = (trident->spdif_bits >> 16) & 0xff;
ucontrol->value.iec958.status[3] = (trident->spdif_bits >> 24) & 0xff;
spin_unlock_irq(&trident->reg_lock);
return 0;
}
static int snd_trident_spdif_default_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
unsigned int val;
int change;
val = (ucontrol->value.iec958.status[0] << 0) |
(ucontrol->value.iec958.status[1] << 8) |
(ucontrol->value.iec958.status[2] << 16) |
(ucontrol->value.iec958.status[3] << 24);
spin_lock_irq(&trident->reg_lock);
change = trident->spdif_bits != val;
trident->spdif_bits = val;
if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
if ((inb(TRID_REG(trident, NX_SPCTRL_SPCSO + 3)) & 0x10) == 0)
outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
} else {
if (trident->spdif == NULL)
outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
}
spin_unlock_irq(&trident->reg_lock);
return change;
}
static struct snd_kcontrol_new snd_trident_spdif_default __devinitdata =
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
.info = snd_trident_spdif_default_info,
.get = snd_trident_spdif_default_get,
.put = snd_trident_spdif_default_put
};
/*---------------------------------------------------------------------------
snd_trident_spdif_mask
Description: put/get the S/PDIF mask
---------------------------------------------------------------------------*/
static int snd_trident_spdif_mask_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}
static int snd_trident_spdif_mask_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.iec958.status[0] = 0xff;
ucontrol->value.iec958.status[1] = 0xff;
ucontrol->value.iec958.status[2] = 0xff;
ucontrol->value.iec958.status[3] = 0xff;
return 0;
}
static struct snd_kcontrol_new snd_trident_spdif_mask __devinitdata =
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
.info = snd_trident_spdif_mask_info,
.get = snd_trident_spdif_mask_get,
};
/*---------------------------------------------------------------------------
snd_trident_spdif_stream
Description: put/get the S/PDIF stream settings
---------------------------------------------------------------------------*/
static int snd_trident_spdif_stream_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}
static int snd_trident_spdif_stream_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
spin_lock_irq(&trident->reg_lock);
ucontrol->value.iec958.status[0] = (trident->spdif_pcm_bits >> 0) & 0xff;
ucontrol->value.iec958.status[1] = (trident->spdif_pcm_bits >> 8) & 0xff;
ucontrol->value.iec958.status[2] = (trident->spdif_pcm_bits >> 16) & 0xff;
ucontrol->value.iec958.status[3] = (trident->spdif_pcm_bits >> 24) & 0xff;
spin_unlock_irq(&trident->reg_lock);
return 0;
}
static int snd_trident_spdif_stream_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
unsigned int val;
int change;
val = (ucontrol->value.iec958.status[0] << 0) |
(ucontrol->value.iec958.status[1] << 8) |
(ucontrol->value.iec958.status[2] << 16) |
(ucontrol->value.iec958.status[3] << 24);
spin_lock_irq(&trident->reg_lock);
change = trident->spdif_pcm_bits != val;
trident->spdif_pcm_bits = val;
if (trident->spdif != NULL) {
if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS));
} else {
outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
}
}
spin_unlock_irq(&trident->reg_lock);
return change;
}
static struct snd_kcontrol_new snd_trident_spdif_stream __devinitdata =
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM),
.info = snd_trident_spdif_stream_info,
.get = snd_trident_spdif_stream_get,
.put = snd_trident_spdif_stream_put
};
/*---------------------------------------------------------------------------
snd_trident_ac97_control
Description: enable/disable rear path for ac97
---------------------------------------------------------------------------*/
#define snd_trident_ac97_control_info snd_ctl_boolean_mono_info
static int snd_trident_ac97_control_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
unsigned char val;
spin_lock_irq(&trident->reg_lock);
val = trident->ac97_ctrl = inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
ucontrol->value.integer.value[0] = (val & (1 << kcontrol->private_value)) ? 1 : 0;
spin_unlock_irq(&trident->reg_lock);
return 0;
}
static int snd_trident_ac97_control_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
unsigned char val;
int change = 0;
spin_lock_irq(&trident->reg_lock);
val = trident->ac97_ctrl = inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
val &= ~(1 << kcontrol->private_value);
if (ucontrol->value.integer.value[0])
val |= 1 << kcontrol->private_value;
change = val != trident->ac97_ctrl;
trident->ac97_ctrl = val;
outl(trident->ac97_ctrl = val, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
spin_unlock_irq(&trident->reg_lock);
return change;
}
static struct snd_kcontrol_new snd_trident_ac97_rear_control __devinitdata =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Rear Path",
.info = snd_trident_ac97_control_info,
.get = snd_trident_ac97_control_get,
.put = snd_trident_ac97_control_put,
.private_value = 4,
};
/*---------------------------------------------------------------------------
snd_trident_vol_control
Description: wave & music volume control
---------------------------------------------------------------------------*/
static int snd_trident_vol_control_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 255;
return 0;
}
static int snd_trident_vol_control_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
unsigned int val;
val = trident->musicvol_wavevol;
ucontrol->value.integer.value[0] = 255 - ((val >> kcontrol->private_value) & 0xff);
ucontrol->value.integer.value[1] = 255 - ((val >> (kcontrol->private_value + 8)) & 0xff);
return 0;
}
static const DECLARE_TLV_DB_SCALE(db_scale_gvol, -6375, 25, 0);
static int snd_trident_vol_control_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
unsigned int val;
int change = 0;
spin_lock_irq(&trident->reg_lock);
val = trident->musicvol_wavevol;
val &= ~(0xffff << kcontrol->private_value);
val |= ((255 - (ucontrol->value.integer.value[0] & 0xff)) |
((255 - (ucontrol->value.integer.value[1] & 0xff)) << 8)) << kcontrol->private_value;
change = val != trident->musicvol_wavevol;
outl(trident->musicvol_wavevol = val, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
spin_unlock_irq(&trident->reg_lock);
return change;
}
static struct snd_kcontrol_new snd_trident_vol_music_control __devinitdata =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Music Playback Volume",
.info = snd_trident_vol_control_info,
.get = snd_trident_vol_control_get,
.put = snd_trident_vol_control_put,
.private_value = 16,
.tlv = { .p = db_scale_gvol },
};
static struct snd_kcontrol_new snd_trident_vol_wave_control __devinitdata =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Wave Playback Volume",
.info = snd_trident_vol_control_info,
.get = snd_trident_vol_control_get,
.put = snd_trident_vol_control_put,
.private_value = 0,
.tlv = { .p = db_scale_gvol },
};
/*---------------------------------------------------------------------------
snd_trident_pcm_vol_control
Description: PCM front volume control
---------------------------------------------------------------------------*/
static int snd_trident_pcm_vol_control_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 255;
if (trident->device == TRIDENT_DEVICE_ID_SI7018)
uinfo->value.integer.max = 1023;
return 0;
}
static int snd_trident_pcm_vol_control_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
ucontrol->value.integer.value[0] = 1023 - mix->vol;
} else {
ucontrol->value.integer.value[0] = 255 - (mix->vol>>2);
}
return 0;
}
static int snd_trident_pcm_vol_control_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
unsigned int val;
int change = 0;
if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
val = 1023 - (ucontrol->value.integer.value[0] & 1023);
} else {
val = (255 - (ucontrol->value.integer.value[0] & 255)) << 2;
}
spin_lock_irq(&trident->reg_lock);
change = val != mix->vol;
mix->vol = val;
if (mix->voice != NULL)
snd_trident_write_vol_reg(trident, mix->voice, val);
spin_unlock_irq(&trident->reg_lock);
return change;
}
static struct snd_kcontrol_new snd_trident_pcm_vol_control __devinitdata =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Front Playback Volume",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.count = 32,
.info = snd_trident_pcm_vol_control_info,
.get = snd_trident_pcm_vol_control_get,
.put = snd_trident_pcm_vol_control_put,
/* FIXME: no tlv yet */
};
/*---------------------------------------------------------------------------
snd_trident_pcm_pan_control
Description: PCM front pan control
---------------------------------------------------------------------------*/
static int snd_trident_pcm_pan_control_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 127;
return 0;
}
static int snd_trident_pcm_pan_control_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
ucontrol->value.integer.value[0] = mix->pan;
if (ucontrol->value.integer.value[0] & 0x40) {
ucontrol->value.integer.value[0] = (0x3f - (ucontrol->value.integer.value[0] & 0x3f));
} else {
ucontrol->value.integer.value[0] |= 0x40;
}
return 0;
}
static int snd_trident_pcm_pan_control_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
unsigned char val;
int change = 0;
if (ucontrol->value.integer.value[0] & 0x40)
val = ucontrol->value.integer.value[0] & 0x3f;
else
val = (0x3f - (ucontrol->value.integer.value[0] & 0x3f)) | 0x40;
spin_lock_irq(&trident->reg_lock);
change = val != mix->pan;
mix->pan = val;
if (mix->voice != NULL)
snd_trident_write_pan_reg(trident, mix->voice, val);
spin_unlock_irq(&trident->reg_lock);
return change;
}
static struct snd_kcontrol_new snd_trident_pcm_pan_control __devinitdata =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Pan Playback Control",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.count = 32,
.info = snd_trident_pcm_pan_control_info,
.get = snd_trident_pcm_pan_control_get,
.put = snd_trident_pcm_pan_control_put,
};
/*---------------------------------------------------------------------------
snd_trident_pcm_rvol_control
Description: PCM reverb volume control
---------------------------------------------------------------------------*/
static int snd_trident_pcm_rvol_control_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 127;
return 0;
}
static int snd_trident_pcm_rvol_control_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
ucontrol->value.integer.value[0] = 127 - mix->rvol;
return 0;
}
static int snd_trident_pcm_rvol_control_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
unsigned short val;
int change = 0;
val = 0x7f - (ucontrol->value.integer.value[0] & 0x7f);
spin_lock_irq(&trident->reg_lock);
change = val != mix->rvol;
mix->rvol = val;
if (mix->voice != NULL)
snd_trident_write_rvol_reg(trident, mix->voice, val);
spin_unlock_irq(&trident->reg_lock);
return change;
}
static const DECLARE_TLV_DB_SCALE(db_scale_crvol, -3175, 25, 1);
static struct snd_kcontrol_new snd_trident_pcm_rvol_control __devinitdata =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Reverb Playback Volume",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.count = 32,
.info = snd_trident_pcm_rvol_control_info,
.get = snd_trident_pcm_rvol_control_get,
.put = snd_trident_pcm_rvol_control_put,
.tlv = { .p = db_scale_crvol },
};
/*---------------------------------------------------------------------------
snd_trident_pcm_cvol_control
Description: PCM chorus volume control
---------------------------------------------------------------------------*/
static int snd_trident_pcm_cvol_control_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 127;
return 0;
}
static int snd_trident_pcm_cvol_control_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
ucontrol->value.integer.value[0] = 127 - mix->cvol;
return 0;
}
static int snd_trident_pcm_cvol_control_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
unsigned short val;
int change = 0;
val = 0x7f - (ucontrol->value.integer.value[0] & 0x7f);
spin_lock_irq(&trident->reg_lock);
change = val != mix->cvol;
mix->cvol = val;
if (mix->voice != NULL)
snd_trident_write_cvol_reg(trident, mix->voice, val);
spin_unlock_irq(&trident->reg_lock);
return change;
}
static struct snd_kcontrol_new snd_trident_pcm_cvol_control __devinitdata =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Chorus Playback Volume",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.count = 32,
.info = snd_trident_pcm_cvol_control_info,
.get = snd_trident_pcm_cvol_control_get,
.put = snd_trident_pcm_cvol_control_put,
.tlv = { .p = db_scale_crvol },
};
static void snd_trident_notify_pcm_change1(struct snd_card *card,
struct snd_kcontrol *kctl,
int num, int activate)
{
struct snd_ctl_elem_id id;
if (! kctl)
return;
if (activate)
kctl->vd[num].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
else
kctl->vd[num].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE |
SNDRV_CTL_EVENT_MASK_INFO,
snd_ctl_build_ioff(&id, kctl, num));
}
static void snd_trident_notify_pcm_change(struct snd_trident *trident,
struct snd_trident_pcm_mixer *tmix,
int num, int activate)
{
snd_trident_notify_pcm_change1(trident->card, trident->ctl_vol, num, activate);
snd_trident_notify_pcm_change1(trident->card, trident->ctl_pan, num, activate);
snd_trident_notify_pcm_change1(trident->card, trident->ctl_rvol, num, activate);
snd_trident_notify_pcm_change1(trident->card, trident->ctl_cvol, num, activate);
}
static int snd_trident_pcm_mixer_build(struct snd_trident *trident,
struct snd_trident_voice *voice,
struct snd_pcm_substream *substream)
{
struct snd_trident_pcm_mixer *tmix;
snd_assert(trident != NULL && voice != NULL && substream != NULL, return -EINVAL);
tmix = &trident->pcm_mixer[substream->number];
tmix->voice = voice;
tmix->vol = T4D_DEFAULT_PCM_VOL;
tmix->pan = T4D_DEFAULT_PCM_PAN;
tmix->rvol = T4D_DEFAULT_PCM_RVOL;
tmix->cvol = T4D_DEFAULT_PCM_CVOL;
snd_trident_notify_pcm_change(trident, tmix, substream->number, 1);
return 0;
}
static int snd_trident_pcm_mixer_free(struct snd_trident *trident, struct snd_trident_voice *voice, struct snd_pcm_substream *substream)
{
struct snd_trident_pcm_mixer *tmix;
snd_assert(trident != NULL && substream != NULL, return -EINVAL);
tmix = &trident->pcm_mixer[substream->number];
tmix->voice = NULL;
snd_trident_notify_pcm_change(trident, tmix, substream->number, 0);
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_mixer
Description: This routine registers the 4DWave device for mixer support.
Parameters: trident - pointer to target device class for 4DWave.
Returns: None
---------------------------------------------------------------------------*/
static int __devinit snd_trident_mixer(struct snd_trident * trident, int pcm_spdif_device)
{
struct snd_ac97_template _ac97;
struct snd_card *card = trident->card;
struct snd_kcontrol *kctl;
struct snd_ctl_elem_value *uctl;
int idx, err, retries = 2;
static struct snd_ac97_bus_ops ops = {
.write = snd_trident_codec_write,
.read = snd_trident_codec_read,
};
uctl = kzalloc(sizeof(*uctl), GFP_KERNEL);
if (!uctl)
return -ENOMEM;
if ((err = snd_ac97_bus(trident->card, 0, &ops, NULL, &trident->ac97_bus)) < 0)
goto __out;
memset(&_ac97, 0, sizeof(_ac97));
_ac97.private_data = trident;
trident->ac97_detect = 1;
__again:
if ((err = snd_ac97_mixer(trident->ac97_bus, &_ac97, &trident->ac97)) < 0) {
if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
if ((err = snd_trident_sis_reset(trident)) < 0)
goto __out;
if (retries-- > 0)
goto __again;
err = -EIO;
}
goto __out;
}
/* secondary codec? */
if (trident->device == TRIDENT_DEVICE_ID_SI7018 &&
(inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & SI_AC97_PRIMARY_READY) != 0) {
_ac97.num = 1;
err = snd_ac97_mixer(trident->ac97_bus, &_ac97, &trident->ac97_sec);
if (err < 0)
snd_printk(KERN_ERR "SI7018: the secondary codec - invalid access\n");
#if 0 // only for my testing purpose --jk
{
struct snd_ac97 *mc97;
err = snd_ac97_modem(trident->card, &_ac97, &mc97);
if (err < 0)
snd_printk(KERN_ERR "snd_ac97_modem returned error %i\n", err);
}
#endif
}
trident->ac97_detect = 0;
if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_trident_vol_wave_control, trident))) < 0)
goto __out;
kctl->put(kctl, uctl);
if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_trident_vol_music_control, trident))) < 0)
goto __out;
kctl->put(kctl, uctl);
outl(trident->musicvol_wavevol = 0x00000000, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
} else {
outl(trident->musicvol_wavevol = 0xffff0000, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
}
for (idx = 0; idx < 32; idx++) {
struct snd_trident_pcm_mixer *tmix;
tmix = &trident->pcm_mixer[idx];
tmix->voice = NULL;
}
if ((trident->ctl_vol = snd_ctl_new1(&snd_trident_pcm_vol_control, trident)) == NULL)
goto __nomem;
if ((err = snd_ctl_add(card, trident->ctl_vol)))
goto __out;
if ((trident->ctl_pan = snd_ctl_new1(&snd_trident_pcm_pan_control, trident)) == NULL)
goto __nomem;
if ((err = snd_ctl_add(card, trident->ctl_pan)))
goto __out;
if ((trident->ctl_rvol = snd_ctl_new1(&snd_trident_pcm_rvol_control, trident)) == NULL)
goto __nomem;
if ((err = snd_ctl_add(card, trident->ctl_rvol)))
goto __out;
if ((trident->ctl_cvol = snd_ctl_new1(&snd_trident_pcm_cvol_control, trident)) == NULL)
goto __nomem;
if ((err = snd_ctl_add(card, trident->ctl_cvol)))
goto __out;
if (trident->device == TRIDENT_DEVICE_ID_NX) {
if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_trident_ac97_rear_control, trident))) < 0)
goto __out;
kctl->put(kctl, uctl);
}
if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018) {
kctl = snd_ctl_new1(&snd_trident_spdif_control, trident);
if (kctl == NULL) {
err = -ENOMEM;
goto __out;
}
if (trident->ac97->ext_id & AC97_EI_SPDIF)
kctl->id.index++;
if (trident->ac97_sec && (trident->ac97_sec->ext_id & AC97_EI_SPDIF))
kctl->id.index++;
idx = kctl->id.index;
if ((err = snd_ctl_add(card, kctl)) < 0)
goto __out;
kctl->put(kctl, uctl);
kctl = snd_ctl_new1(&snd_trident_spdif_default, trident);
if (kctl == NULL) {
err = -ENOMEM;
goto __out;
}
kctl->id.index = idx;
kctl->id.device = pcm_spdif_device;
if ((err = snd_ctl_add(card, kctl)) < 0)
goto __out;
kctl = snd_ctl_new1(&snd_trident_spdif_mask, trident);
if (kctl == NULL) {
err = -ENOMEM;
goto __out;
}
kctl->id.index = idx;
kctl->id.device = pcm_spdif_device;
if ((err = snd_ctl_add(card, kctl)) < 0)
goto __out;
kctl = snd_ctl_new1(&snd_trident_spdif_stream, trident);
if (kctl == NULL) {
err = -ENOMEM;
goto __out;
}
kctl->id.index = idx;
kctl->id.device = pcm_spdif_device;
if ((err = snd_ctl_add(card, kctl)) < 0)
goto __out;
trident->spdif_pcm_ctl = kctl;
}
err = 0;
goto __out;
__nomem:
err = -ENOMEM;
__out:
kfree(uctl);
return err;
}
/*
* gameport interface
*/
#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
static unsigned char snd_trident_gameport_read(struct gameport *gameport)
{
struct snd_trident *chip = gameport_get_port_data(gameport);
snd_assert(chip, return 0);
return inb(TRID_REG(chip, GAMEPORT_LEGACY));
}
static void snd_trident_gameport_trigger(struct gameport *gameport)
{
struct snd_trident *chip = gameport_get_port_data(gameport);
snd_assert(chip, return);
outb(0xff, TRID_REG(chip, GAMEPORT_LEGACY));
}
static int snd_trident_gameport_cooked_read(struct gameport *gameport, int *axes, int *buttons)
{
struct snd_trident *chip = gameport_get_port_data(gameport);
int i;
snd_assert(chip, return 0);
*buttons = (~inb(TRID_REG(chip, GAMEPORT_LEGACY)) >> 4) & 0xf;
for (i = 0; i < 4; i++) {
axes[i] = inw(TRID_REG(chip, GAMEPORT_AXES + i * 2));
if (axes[i] == 0xffff) axes[i] = -1;
}
return 0;
}
static int snd_trident_gameport_open(struct gameport *gameport, int mode)
{
struct snd_trident *chip = gameport_get_port_data(gameport);
snd_assert(chip, return 0);
switch (mode) {
case GAMEPORT_MODE_COOKED:
outb(GAMEPORT_MODE_ADC, TRID_REG(chip, GAMEPORT_GCR));
msleep(20);
return 0;
case GAMEPORT_MODE_RAW:
outb(0, TRID_REG(chip, GAMEPORT_GCR));
return 0;
default:
return -1;
}
}
int __devinit snd_trident_create_gameport(struct snd_trident *chip)
{
struct gameport *gp;
chip->gameport = gp = gameport_allocate_port();
if (!gp) {
printk(KERN_ERR "trident: cannot allocate memory for gameport\n");
return -ENOMEM;
}
gameport_set_name(gp, "Trident 4DWave");
gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci));
gameport_set_dev_parent(gp, &chip->pci->dev);
gameport_set_port_data(gp, chip);
gp->fuzz = 64;
gp->read = snd_trident_gameport_read;
gp->trigger = snd_trident_gameport_trigger;
gp->cooked_read = snd_trident_gameport_cooked_read;
gp->open = snd_trident_gameport_open;
gameport_register_port(gp);
return 0;
}
static inline void snd_trident_free_gameport(struct snd_trident *chip)
{
if (chip->gameport) {
gameport_unregister_port(chip->gameport);
chip->gameport = NULL;
}
}
#else
int __devinit snd_trident_create_gameport(struct snd_trident *chip) { return -ENOSYS; }
static inline void snd_trident_free_gameport(struct snd_trident *chip) { }
#endif /* CONFIG_GAMEPORT */
/*
* delay for 1 tick
*/
static inline void do_delay(struct snd_trident *chip)
{
schedule_timeout_uninterruptible(1);
}
/*
* SiS reset routine
*/
static int snd_trident_sis_reset(struct snd_trident *trident)
{
unsigned long end_time;
unsigned int i;
int r;
r = trident->in_suspend ? 0 : 2; /* count of retries */
__si7018_retry:
pci_write_config_byte(trident->pci, 0x46, 0x04); /* SOFTWARE RESET */
udelay(100);
pci_write_config_byte(trident->pci, 0x46, 0x00);
udelay(100);
/* disable AC97 GPIO interrupt */
outb(0x00, TRID_REG(trident, SI_AC97_GPIO));
/* initialize serial interface, force cold reset */
i = PCMOUT|SURROUT|CENTEROUT|LFEOUT|SECONDARY_ID|COLD_RESET;
outl(i, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
udelay(1000);
/* remove cold reset */
i &= ~COLD_RESET;
outl(i, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
udelay(2000);
/* wait, until the codec is ready */
end_time = (jiffies + (HZ * 3) / 4) + 1;
do {
if ((inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & SI_AC97_PRIMARY_READY) != 0)
goto __si7018_ok;
do_delay(trident);
} while (time_after_eq(end_time, jiffies));
snd_printk(KERN_ERR "AC'97 codec ready error [0x%x]\n", inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)));
if (r-- > 0) {
end_time = jiffies + HZ;
do {
do_delay(trident);
} while (time_after_eq(end_time, jiffies));
goto __si7018_retry;
}
__si7018_ok:
/* wait for the second codec */
do {
if ((inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & SI_AC97_SECONDARY_READY) != 0)
break;
do_delay(trident);
} while (time_after_eq(end_time, jiffies));
/* enable 64 channel mode */
outl(BANK_B_EN, TRID_REG(trident, T4D_LFO_GC_CIR));
return 0;
}
/*
* /proc interface
*/
static void snd_trident_proc_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_trident *trident = entry->private_data;
char *s;
switch (trident->device) {
case TRIDENT_DEVICE_ID_SI7018:
s = "SiS 7018 Audio";
break;
case TRIDENT_DEVICE_ID_DX:
s = "Trident 4DWave PCI DX";
break;
case TRIDENT_DEVICE_ID_NX:
s = "Trident 4DWave PCI NX";
break;
default:
s = "???";
}
snd_iprintf(buffer, "%s\n\n", s);
snd_iprintf(buffer, "Spurious IRQs : %d\n", trident->spurious_irq_count);
snd_iprintf(buffer, "Spurious IRQ dlta: %d\n", trident->spurious_irq_max_delta);
if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018)
snd_iprintf(buffer, "IEC958 Mixer Out : %s\n", trident->spdif_ctrl == 0x28 ? "on" : "off");
if (trident->device == TRIDENT_DEVICE_ID_NX) {
snd_iprintf(buffer, "Rear Speakers : %s\n", trident->ac97_ctrl & 0x00000010 ? "on" : "off");
if (trident->tlb.entries) {
snd_iprintf(buffer,"\nVirtual Memory\n");
snd_iprintf(buffer, "Memory Maximum : %d\n", trident->tlb.memhdr->size);
snd_iprintf(buffer, "Memory Used : %d\n", trident->tlb.memhdr->used);
snd_iprintf(buffer, "Memory Free : %d\n", snd_util_mem_avail(trident->tlb.memhdr));
}
}
}
static void __devinit snd_trident_proc_init(struct snd_trident * trident)
{
struct snd_info_entry *entry;
const char *s = "trident";
if (trident->device == TRIDENT_DEVICE_ID_SI7018)
s = "sis7018";
if (! snd_card_proc_new(trident->card, s, &entry))
snd_info_set_text_ops(entry, trident, snd_trident_proc_read);
}
static int snd_trident_dev_free(struct snd_device *device)
{
struct snd_trident *trident = device->device_data;
return snd_trident_free(trident);
}
/*---------------------------------------------------------------------------
snd_trident_tlb_alloc
Description: Allocate and set up the TLB page table on 4D NX.
Each entry has 4 bytes (physical PCI address).
Parameters: trident - pointer to target device class for 4DWave.
Returns: 0 or negative error code
---------------------------------------------------------------------------*/
static int __devinit snd_trident_tlb_alloc(struct snd_trident *trident)
{
int i;
/* TLB array must be aligned to 16kB !!! so we allocate
32kB region and correct offset when necessary */
if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci),
2 * SNDRV_TRIDENT_MAX_PAGES * 4, &trident->tlb.buffer) < 0) {
snd_printk(KERN_ERR "trident: unable to allocate TLB buffer\n");
return -ENOMEM;
}
trident->tlb.entries = (unsigned int*)ALIGN((unsigned long)trident->tlb.buffer.area, SNDRV_TRIDENT_MAX_PAGES * 4);
trident->tlb.entries_dmaaddr = ALIGN(trident->tlb.buffer.addr, SNDRV_TRIDENT_MAX_PAGES * 4);
/* allocate shadow TLB page table (virtual addresses) */
trident->tlb.shadow_entries = vmalloc(SNDRV_TRIDENT_MAX_PAGES*sizeof(unsigned long));
if (trident->tlb.shadow_entries == NULL) {
snd_printk(KERN_ERR "trident: unable to allocate shadow TLB entries\n");
return -ENOMEM;
}
/* allocate and setup silent page and initialise TLB entries */
if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci),
SNDRV_TRIDENT_PAGE_SIZE, &trident->tlb.silent_page) < 0) {
snd_printk(KERN_ERR "trident: unable to allocate silent page\n");
return -ENOMEM;
}
memset(trident->tlb.silent_page.area, 0, SNDRV_TRIDENT_PAGE_SIZE);
for (i = 0; i < SNDRV_TRIDENT_MAX_PAGES; i++) {
trident->tlb.entries[i] = cpu_to_le32(trident->tlb.silent_page.addr & ~(SNDRV_TRIDENT_PAGE_SIZE-1));
trident->tlb.shadow_entries[i] = (unsigned long)trident->tlb.silent_page.area;
}
/* use emu memory block manager code to manage tlb page allocation */
trident->tlb.memhdr = snd_util_memhdr_new(SNDRV_TRIDENT_PAGE_SIZE * SNDRV_TRIDENT_MAX_PAGES);
if (trident->tlb.memhdr == NULL)
return -ENOMEM;
trident->tlb.memhdr->block_extra_size = sizeof(struct snd_trident_memblk_arg);
return 0;
}
/*
* initialize 4D DX chip
*/
static void snd_trident_stop_all_voices(struct snd_trident *trident)
{
outl(0xffffffff, TRID_REG(trident, T4D_STOP_A));
outl(0xffffffff, TRID_REG(trident, T4D_STOP_B));
outl(0, TRID_REG(trident, T4D_AINTEN_A));
outl(0, TRID_REG(trident, T4D_AINTEN_B));
}
static int snd_trident_4d_dx_init(struct snd_trident *trident)
{
struct pci_dev *pci = trident->pci;
unsigned long end_time;
/* reset the legacy configuration and whole audio/wavetable block */
pci_write_config_dword(pci, 0x40, 0); /* DDMA */
pci_write_config_byte(pci, 0x44, 0); /* ports */
pci_write_config_byte(pci, 0x45, 0); /* Legacy DMA */
pci_write_config_byte(pci, 0x46, 4); /* reset */
udelay(100);
pci_write_config_byte(pci, 0x46, 0); /* release reset */
udelay(100);
/* warm reset of the AC'97 codec */
outl(0x00000001, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
udelay(100);
outl(0x00000000, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
/* DAC on, disable SB IRQ and try to force ADC valid signal */
trident->ac97_ctrl = 0x0000004a;
outl(trident->ac97_ctrl, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
/* wait, until the codec is ready */
end_time = (jiffies + (HZ * 3) / 4) + 1;
do {
if ((inl(TRID_REG(trident, DX_ACR2_AC97_COM_STAT)) & 0x0010) != 0)
goto __dx_ok;
do_delay(trident);
} while (time_after_eq(end_time, jiffies));
snd_printk(KERN_ERR "AC'97 codec ready error\n");
return -EIO;
__dx_ok:
snd_trident_stop_all_voices(trident);
return 0;
}
/*
* initialize 4D NX chip
*/
static int snd_trident_4d_nx_init(struct snd_trident *trident)
{
struct pci_dev *pci = trident->pci;
unsigned long end_time;
/* reset the legacy configuration and whole audio/wavetable block */
pci_write_config_dword(pci, 0x40, 0); /* DDMA */
pci_write_config_byte(pci, 0x44, 0); /* ports */
pci_write_config_byte(pci, 0x45, 0); /* Legacy DMA */
pci_write_config_byte(pci, 0x46, 1); /* reset */
udelay(100);
pci_write_config_byte(pci, 0x46, 0); /* release reset */
udelay(100);
/* warm reset of the AC'97 codec */
outl(0x00000001, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
udelay(100);
outl(0x00000000, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
/* wait, until the codec is ready */
end_time = (jiffies + (HZ * 3) / 4) + 1;
do {
if ((inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT)) & 0x0008) != 0)
goto __nx_ok;
do_delay(trident);
} while (time_after_eq(end_time, jiffies));
snd_printk(KERN_ERR "AC'97 codec ready error [0x%x]\n", inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT)));
return -EIO;
__nx_ok:
/* DAC on */
trident->ac97_ctrl = 0x00000002;
outl(trident->ac97_ctrl, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
/* disable SB IRQ */
outl(NX_SB_IRQ_DISABLE, TRID_REG(trident, T4D_MISCINT));
snd_trident_stop_all_voices(trident);
if (trident->tlb.entries != NULL) {
unsigned int i;
/* enable virtual addressing via TLB */
i = trident->tlb.entries_dmaaddr;
i |= 0x00000001;
outl(i, TRID_REG(trident, NX_TLBC));
} else {
outl(0, TRID_REG(trident, NX_TLBC));
}
/* initialize S/PDIF */
outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
return 0;
}
/*
* initialize sis7018 chip
*/
static int snd_trident_sis_init(struct snd_trident *trident)
{
int err;
if ((err = snd_trident_sis_reset(trident)) < 0)
return err;
snd_trident_stop_all_voices(trident);
/* initialize S/PDIF */
outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_create
Description: This routine will create the device specific class for
the 4DWave card. It will also perform basic initialization.
Parameters: card - which card to create
pci - interface to PCI bus resource info
dma1ptr - playback dma buffer
dma2ptr - capture dma buffer
irqptr - interrupt resource info
Returns: 4DWave device class private data
---------------------------------------------------------------------------*/
int __devinit snd_trident_create(struct snd_card *card,
struct pci_dev *pci,
int pcm_streams,
int pcm_spdif_device,
int max_wavetable_size,
struct snd_trident ** rtrident)
{
struct snd_trident *trident;
int i, err;
struct snd_trident_voice *voice;
struct snd_trident_pcm_mixer *tmix;
static struct snd_device_ops ops = {
.dev_free = snd_trident_dev_free,
};
*rtrident = NULL;
/* enable PCI device */
if ((err = pci_enable_device(pci)) < 0)
return err;
/* check, if we can restrict PCI DMA transfers to 30 bits */
if (pci_set_dma_mask(pci, DMA_30BIT_MASK) < 0 ||
pci_set_consistent_dma_mask(pci, DMA_30BIT_MASK) < 0) {
snd_printk(KERN_ERR "architecture does not support 30bit PCI busmaster DMA\n");
pci_disable_device(pci);
return -ENXIO;
}
trident = kzalloc(sizeof(*trident), GFP_KERNEL);
if (trident == NULL) {
pci_disable_device(pci);
return -ENOMEM;
}
trident->device = (pci->vendor << 16) | pci->device;
trident->card = card;
trident->pci = pci;
spin_lock_init(&trident->reg_lock);
spin_lock_init(&trident->event_lock);
spin_lock_init(&trident->voice_alloc);
if (pcm_streams < 1)
pcm_streams = 1;
if (pcm_streams > 32)
pcm_streams = 32;
trident->ChanPCM = pcm_streams;
if (max_wavetable_size < 0 )
max_wavetable_size = 0;
trident->synth.max_size = max_wavetable_size * 1024;
trident->irq = -1;
trident->midi_port = TRID_REG(trident, T4D_MPU401_BASE);
pci_set_master(pci);
if ((err = pci_request_regions(pci, "Trident Audio")) < 0) {
kfree(trident);
pci_disable_device(pci);
return err;
}
trident->port = pci_resource_start(pci, 0);
if (request_irq(pci->irq, snd_trident_interrupt, IRQF_SHARED,
"Trident Audio", trident)) {
snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
snd_trident_free(trident);
return -EBUSY;
}
trident->irq = pci->irq;
/* allocate 16k-aligned TLB for NX cards */
trident->tlb.entries = NULL;
trident->tlb.buffer.area = NULL;
if (trident->device == TRIDENT_DEVICE_ID_NX) {
if ((err = snd_trident_tlb_alloc(trident)) < 0) {
snd_trident_free(trident);
return err;
}
}
trident->spdif_bits = trident->spdif_pcm_bits = SNDRV_PCM_DEFAULT_CON_SPDIF;
/* initialize chip */
switch (trident->device) {
case TRIDENT_DEVICE_ID_DX:
err = snd_trident_4d_dx_init(trident);
break;
case TRIDENT_DEVICE_ID_NX:
err = snd_trident_4d_nx_init(trident);
break;
case TRIDENT_DEVICE_ID_SI7018:
err = snd_trident_sis_init(trident);
break;
default:
snd_BUG();
break;
}
if (err < 0) {
snd_trident_free(trident);
return err;
}
if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, trident, &ops)) < 0) {
snd_trident_free(trident);
return err;
}
if ((err = snd_trident_mixer(trident, pcm_spdif_device)) < 0)
return err;
/* initialise synth voices */
for (i = 0; i < 64; i++) {
voice = &trident->synth.voices[i];
voice->number = i;
voice->trident = trident;
}
/* initialize pcm mixer entries */
for (i = 0; i < 32; i++) {
tmix = &trident->pcm_mixer[i];
tmix->vol = T4D_DEFAULT_PCM_VOL;
tmix->pan = T4D_DEFAULT_PCM_PAN;
tmix->rvol = T4D_DEFAULT_PCM_RVOL;
tmix->cvol = T4D_DEFAULT_PCM_CVOL;
}
snd_trident_enable_eso(trident);
snd_trident_proc_init(trident);
snd_card_set_dev(card, &pci->dev);
*rtrident = trident;
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_free
Description: This routine will free the device specific class for
the 4DWave card.
Parameters: trident - device specific private data for 4DWave card
Returns: None.
---------------------------------------------------------------------------*/
static int snd_trident_free(struct snd_trident *trident)
{
snd_trident_free_gameport(trident);
snd_trident_disable_eso(trident);
// Disable S/PDIF out
if (trident->device == TRIDENT_DEVICE_ID_NX)
outb(0x00, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
else if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
outl(0, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
}
if (trident->irq >= 0)
free_irq(trident->irq, trident);
if (trident->tlb.buffer.area) {
outl(0, TRID_REG(trident, NX_TLBC));
if (trident->tlb.memhdr)
snd_util_memhdr_free(trident->tlb.memhdr);
if (trident->tlb.silent_page.area)
snd_dma_free_pages(&trident->tlb.silent_page);
vfree(trident->tlb.shadow_entries);
snd_dma_free_pages(&trident->tlb.buffer);
}
pci_release_regions(trident->pci);
pci_disable_device(trident->pci);
kfree(trident);
return 0;
}
/*---------------------------------------------------------------------------
snd_trident_interrupt
Description: ISR for Trident 4DWave device
Parameters: trident - device specific private data for 4DWave card
Problems: It seems that Trident chips generates interrupts more than
one time in special cases. The spurious interrupts are
detected via sample timer (T4D_STIMER) and computing
corresponding delta value. The limits are detected with
the method try & fail so it is possible that it won't
work on all computers. [jaroslav]
Returns: None.
---------------------------------------------------------------------------*/
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 13:55:46 +00:00
static irqreturn_t snd_trident_interrupt(int irq, void *dev_id)
{
struct snd_trident *trident = dev_id;
unsigned int audio_int, chn_int, stimer, channel, mask, tmp;
int delta;
struct snd_trident_voice *voice;
audio_int = inl(TRID_REG(trident, T4D_MISCINT));
if ((audio_int & (ADDRESS_IRQ|MPU401_IRQ)) == 0)
return IRQ_NONE;
if (audio_int & ADDRESS_IRQ) {
// get interrupt status for all channels
spin_lock(&trident->reg_lock);
stimer = inl(TRID_REG(trident, T4D_STIMER)) & 0x00ffffff;
chn_int = inl(TRID_REG(trident, T4D_AINT_A));
if (chn_int == 0)
goto __skip1;
outl(chn_int, TRID_REG(trident, T4D_AINT_A)); /* ack */
__skip1:
chn_int = inl(TRID_REG(trident, T4D_AINT_B));
if (chn_int == 0)
goto __skip2;
for (channel = 63; channel >= 32; channel--) {
mask = 1 << (channel&0x1f);
if ((chn_int & mask) == 0)
continue;
voice = &trident->synth.voices[channel];
if (!voice->pcm || voice->substream == NULL) {
outl(mask, TRID_REG(trident, T4D_STOP_B));
continue;
}
delta = (int)stimer - (int)voice->stimer;
if (delta < 0)
delta = -delta;
if ((unsigned int)delta < voice->spurious_threshold) {
/* do some statistics here */
trident->spurious_irq_count++;
if (trident->spurious_irq_max_delta < (unsigned int)delta)
trident->spurious_irq_max_delta = delta;
continue;
}
voice->stimer = stimer;
if (voice->isync) {
if (!voice->isync3) {
tmp = inw(TRID_REG(trident, T4D_SBBL_SBCL));
if (trident->bDMAStart & 0x40)
tmp >>= 1;
if (tmp > 0)
tmp = voice->isync_max - tmp;
} else {
tmp = inl(TRID_REG(trident, NX_SPCTRL_SPCSO)) & 0x00ffffff;
}
if (tmp < voice->isync_mark) {
if (tmp > 0x10)
tmp = voice->isync_ESO - 7;
else
tmp = voice->isync_ESO + 2;
/* update ESO for IRQ voice to preserve sync */
snd_trident_stop_voice(trident, voice->number);
snd_trident_write_eso_reg(trident, voice, tmp);
snd_trident_start_voice(trident, voice->number);
}
} else if (voice->isync2) {
voice->isync2 = 0;
/* write original ESO and update CSO for IRQ voice to preserve sync */
snd_trident_stop_voice(trident, voice->number);
snd_trident_write_cso_reg(trident, voice, voice->isync_mark);
snd_trident_write_eso_reg(trident, voice, voice->ESO);
snd_trident_start_voice(trident, voice->number);
}
#if 0
if (voice->extra) {
/* update CSO for extra voice to preserve sync */
snd_trident_stop_voice(trident, voice->extra->number);
snd_trident_write_cso_reg(trident, voice->extra, 0);
snd_trident_start_voice(trident, voice->extra->number);
}
#endif
spin_unlock(&trident->reg_lock);
snd_pcm_period_elapsed(voice->substream);
spin_lock(&trident->reg_lock);
}
outl(chn_int, TRID_REG(trident, T4D_AINT_B)); /* ack */
__skip2:
spin_unlock(&trident->reg_lock);
}
if (audio_int & MPU401_IRQ) {
if (trident->rmidi) {
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 13:55:46 +00:00
snd_mpu401_uart_interrupt(irq, trident->rmidi->private_data);
} else {
inb(TRID_REG(trident, T4D_MPUR0));
}
}
// outl((ST_TARGET_REACHED | MIXER_OVERFLOW | MIXER_UNDERFLOW), TRID_REG(trident, T4D_MISCINT));
return IRQ_HANDLED;
}
struct snd_trident_voice *snd_trident_alloc_voice(struct snd_trident * trident, int type, int client, int port)
{
struct snd_trident_voice *pvoice;
unsigned long flags;
int idx;
spin_lock_irqsave(&trident->voice_alloc, flags);
if (type == SNDRV_TRIDENT_VOICE_TYPE_PCM) {
idx = snd_trident_allocate_pcm_channel(trident);
if(idx < 0) {
spin_unlock_irqrestore(&trident->voice_alloc, flags);
return NULL;
}
pvoice = &trident->synth.voices[idx];
pvoice->use = 1;
pvoice->pcm = 1;
pvoice->capture = 0;
pvoice->spdif = 0;
pvoice->memblk = NULL;
pvoice->substream = NULL;
spin_unlock_irqrestore(&trident->voice_alloc, flags);
return pvoice;
}
if (type == SNDRV_TRIDENT_VOICE_TYPE_SYNTH) {
idx = snd_trident_allocate_synth_channel(trident);
if(idx < 0) {
spin_unlock_irqrestore(&trident->voice_alloc, flags);
return NULL;
}
pvoice = &trident->synth.voices[idx];
pvoice->use = 1;
pvoice->synth = 1;
pvoice->client = client;
pvoice->port = port;
pvoice->memblk = NULL;
spin_unlock_irqrestore(&trident->voice_alloc, flags);
return pvoice;
}
if (type == SNDRV_TRIDENT_VOICE_TYPE_MIDI) {
}
spin_unlock_irqrestore(&trident->voice_alloc, flags);
return NULL;
}
EXPORT_SYMBOL(snd_trident_alloc_voice);
void snd_trident_free_voice(struct snd_trident * trident, struct snd_trident_voice *voice)
{
unsigned long flags;
void (*private_free)(struct snd_trident_voice *);
void *private_data;
if (voice == NULL || !voice->use)
return;
snd_trident_clear_voices(trident, voice->number, voice->number);
spin_lock_irqsave(&trident->voice_alloc, flags);
private_free = voice->private_free;
private_data = voice->private_data;
voice->private_free = NULL;
voice->private_data = NULL;
if (voice->pcm)
snd_trident_free_pcm_channel(trident, voice->number);
if (voice->synth)
snd_trident_free_synth_channel(trident, voice->number);
voice->use = voice->pcm = voice->synth = voice->midi = 0;
voice->capture = voice->spdif = 0;
voice->sample_ops = NULL;
voice->substream = NULL;
voice->extra = NULL;
spin_unlock_irqrestore(&trident->voice_alloc, flags);
if (private_free)
private_free(voice);
}
EXPORT_SYMBOL(snd_trident_free_voice);
static void snd_trident_clear_voices(struct snd_trident * trident, unsigned short v_min, unsigned short v_max)
{
unsigned int i, val, mask[2] = { 0, 0 };
snd_assert(v_min <= 63, return);
snd_assert(v_max <= 63, return);
for (i = v_min; i <= v_max; i++)
mask[i >> 5] |= 1 << (i & 0x1f);
if (mask[0]) {
outl(mask[0], TRID_REG(trident, T4D_STOP_A));
val = inl(TRID_REG(trident, T4D_AINTEN_A));
outl(val & ~mask[0], TRID_REG(trident, T4D_AINTEN_A));
}
if (mask[1]) {
outl(mask[1], TRID_REG(trident, T4D_STOP_B));
val = inl(TRID_REG(trident, T4D_AINTEN_B));
outl(val & ~mask[1], TRID_REG(trident, T4D_AINTEN_B));
}
}
#ifdef CONFIG_PM
int snd_trident_suspend(struct pci_dev *pci, pm_message_t state)
{
struct snd_card *card = pci_get_drvdata(pci);
struct snd_trident *trident = card->private_data;
trident->in_suspend = 1;
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
snd_pcm_suspend_all(trident->pcm);
snd_pcm_suspend_all(trident->foldback);
snd_pcm_suspend_all(trident->spdif);
snd_ac97_suspend(trident->ac97);
snd_ac97_suspend(trident->ac97_sec);
pci_disable_device(pci);
pci_save_state(pci);
pci_set_power_state(pci, pci_choose_state(pci, state));
return 0;
}
int snd_trident_resume(struct pci_dev *pci)
{
struct snd_card *card = pci_get_drvdata(pci);
struct snd_trident *trident = card->private_data;
pci_set_power_state(pci, PCI_D0);
pci_restore_state(pci);
if (pci_enable_device(pci) < 0) {
printk(KERN_ERR "trident: pci_enable_device failed, "
"disabling device\n");
snd_card_disconnect(card);
return -EIO;
}
pci_set_master(pci);
switch (trident->device) {
case TRIDENT_DEVICE_ID_DX:
snd_trident_4d_dx_init(trident);
break;
case TRIDENT_DEVICE_ID_NX:
snd_trident_4d_nx_init(trident);
break;
case TRIDENT_DEVICE_ID_SI7018:
snd_trident_sis_init(trident);
break;
}
snd_ac97_resume(trident->ac97);
snd_ac97_resume(trident->ac97_sec);
/* restore some registers */
outl(trident->musicvol_wavevol, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
snd_trident_enable_eso(trident);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
trident->in_suspend = 0;
return 0;
}
#endif /* CONFIG_PM */