diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /sound/pci/trident |
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'sound/pci/trident')
-rw-r--r-- | sound/pci/trident/Makefile | 19 | ||||
-rw-r--r-- | sound/pci/trident/trident.c | 196 | ||||
-rw-r--r-- | sound/pci/trident/trident_main.c | 3991 | ||||
-rw-r--r-- | sound/pci/trident/trident_memory.c | 476 | ||||
-rw-r--r-- | sound/pci/trident/trident_synth.c | 1031 |
5 files changed, 5713 insertions, 0 deletions
diff --git a/sound/pci/trident/Makefile b/sound/pci/trident/Makefile new file mode 100644 index 00000000000..65bc5b70323 --- /dev/null +++ b/sound/pci/trident/Makefile @@ -0,0 +1,19 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz> +# + +snd-trident-objs := trident.o trident_main.o trident_memory.o +snd-trident-synth-objs := trident_synth.o + +# +# this function returns: +# "m" - CONFIG_SND_SEQUENCER is m +# <empty string> - CONFIG_SND_SEQUENCER is undefined +# otherwise parameter #1 value +# +sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1))) + +# Toplevel Module Dependency +obj-$(CONFIG_SND_TRIDENT) += snd-trident.o +obj-$(call sequencer,$(CONFIG_SND_TRIDENT)) += snd-trident-synth.o diff --git a/sound/pci/trident/trident.c b/sound/pci/trident/trident.c new file mode 100644 index 00000000000..ad58e08d66e --- /dev/null +++ b/sound/pci/trident/trident.c @@ -0,0 +1,196 @@ +/* + * Driver for Trident 4DWave DX/NX & SiS SI7018 Audio PCI soundcard + * + * Driver was originated by Trident <audio@tridentmicro.com> + * Fri Feb 19 15:55:28 MST 1999 + * + * + * 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 + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/time.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/trident.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, <audio@tridentmicro.com>"); +MODULE_DESCRIPTION("Trident 4D-WaveDX/NX & SiS SI7018"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Trident,4DWave DX}," + "{Trident,4DWave NX}," + "{SiS,SI7018 PCI Audio}," + "{Best Union,Miss Melody 4DWave PCI}," + "{HIS,4DWave PCI}," + "{Warpspeed,ONSpeed 4DWave PCI}," + "{Aztech Systems,PCI 64-Q3D}," + "{Addonics,SV 750}," + "{CHIC,True Sound 4Dwave}," + "{Shark,Predator4D-PCI}," + "{Jaton,SonicWave 4D}," + "{Hoontech,SoundTrack Digital 4DWave NX}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ +static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 32}; +static int wavetable_size[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8192}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Trident 4DWave PCI soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Trident 4DWave PCI soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Trident 4DWave PCI soundcard."); +module_param_array(pcm_channels, int, NULL, 0444); +MODULE_PARM_DESC(pcm_channels, "Number of hardware channels assigned for PCM."); +module_param_array(wavetable_size, int, NULL, 0444); +MODULE_PARM_DESC(wavetable_size, "Maximum memory size in kB for wavetable synth."); + +static struct pci_device_id snd_trident_ids[] = { + { 0x1023, 0x2000, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, }, /* Trident 4DWave DX PCI Audio */ + { 0x1023, 0x2001, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, }, /* Trident 4DWave NX PCI Audio */ + { 0x1039, 0x7018, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, }, /* SiS SI7018 PCI Audio */ + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, snd_trident_ids); + +static int __devinit snd_trident_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + snd_card_t *card; + trident_t *trident; + const char *str; + int err, pcm_dev = 0; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + if (card == NULL) + return -ENOMEM; + + if ((err = snd_trident_create(card, pci, + pcm_channels[dev], + ((pci->vendor << 16) | pci->device) == TRIDENT_DEVICE_ID_SI7018 ? 1 : 2, + wavetable_size[dev], + &trident)) < 0) { + snd_card_free(card); + return err; + } + + switch (trident->device) { + case TRIDENT_DEVICE_ID_DX: + str = "TRID4DWAVEDX"; + break; + case TRIDENT_DEVICE_ID_NX: + str = "TRID4DWAVENX"; + break; + case TRIDENT_DEVICE_ID_SI7018: + str = "SI7018"; + break; + default: + str = "Unknown"; + } + strcpy(card->driver, str); + if (trident->device == TRIDENT_DEVICE_ID_SI7018) { + strcpy(card->shortname, "SiS "); + } else { + strcpy(card->shortname, "Trident "); + } + strcat(card->shortname, card->driver); + sprintf(card->longname, "%s PCI Audio at 0x%lx, irq %d", + card->shortname, trident->port, trident->irq); + + if ((err = snd_trident_pcm(trident, pcm_dev++, NULL)) < 0) { + snd_card_free(card); + return err; + } + switch (trident->device) { + case TRIDENT_DEVICE_ID_DX: + case TRIDENT_DEVICE_ID_NX: + if ((err = snd_trident_foldback_pcm(trident, pcm_dev++, NULL)) < 0) { + snd_card_free(card); + return err; + } + break; + } + if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018) { + if ((err = snd_trident_spdif_pcm(trident, pcm_dev++, NULL)) < 0) { + snd_card_free(card); + return err; + } + } + if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_TRID4DWAVE, + trident->midi_port, 1, + trident->irq, 0, &trident->rmidi)) < 0) { + snd_card_free(card); + return err; + } + +#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)) + if ((err = snd_trident_attach_synthesizer(trident)) < 0) { + snd_card_free(card); + return err; + } +#endif + + snd_trident_create_gameport(trident); + + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static void __devexit snd_trident_remove(struct pci_dev *pci) +{ + snd_card_free(pci_get_drvdata(pci)); + pci_set_drvdata(pci, NULL); +} + +static struct pci_driver driver = { + .name = "Trident4DWaveAudio", + .id_table = snd_trident_ids, + .probe = snd_trident_probe, + .remove = __devexit_p(snd_trident_remove), + SND_PCI_PM_CALLBACKS +}; + +static int __init alsa_card_trident_init(void) +{ + return pci_module_init(&driver); +} + +static void __exit alsa_card_trident_exit(void) +{ + pci_unregister_driver(&driver); +} + +module_init(alsa_card_trident_init) +module_exit(alsa_card_trident_exit) diff --git a/sound/pci/trident/trident_main.c b/sound/pci/trident/trident_main.c new file mode 100644 index 00000000000..ccd5ca2ba16 --- /dev/null +++ b/sound/pci/trident/trident_main.c @@ -0,0 +1,3991 @@ +/* + * Maintained by Jaroslav Kysela <perex@suse.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 <sound/driver.h> +#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 <sound/core.h> +#include <sound/info.h> +#include <sound/control.h> +#include <sound/trident.h> +#include <sound/asoundef.h> + +#include <asm/io.h> + +static int snd_trident_pcm_mixer_build(trident_t *trident, snd_trident_voice_t * voice, snd_pcm_substream_t *substream); +static int snd_trident_pcm_mixer_free(trident_t *trident, snd_trident_voice_t * voice, snd_pcm_substream_t *substream); +static irqreturn_t snd_trident_interrupt(int irq, void *dev_id, struct pt_regs *regs); +#ifdef CONFIG_PM +static int snd_trident_suspend(snd_card_t *card, pm_message_t state); +static int snd_trident_resume(snd_card_t *card); +#endif +static int snd_trident_sis_reset(trident_t *trident); + +static void snd_trident_clear_voices(trident_t * trident, unsigned short v_min, unsigned short v_max); +static int snd_trident_free(trident_t *trident); + +/* + * common I/O routines + */ + + +#if 0 +static void snd_trident_print_voice_regs(trident_t *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(ac97_t *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(ac97_t *ac97, unsigned short reg) +{ + unsigned int data = 0, treg; + unsigned short count = 0xffff; + unsigned long flags; + trident_t *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("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(ac97_t *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(ac97_t *ac97, unsigned short reg, unsigned short wdata) +{ + unsigned int address, data; + unsigned short count = 0xffff; + unsigned long flags; + trident_t *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(trident_t *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(trident_t * 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(trident_t *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(trident_t * 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(trident_t * 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(trident_t * 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)); +} + +/*--------------------------------------------------------------------------- + void snd_trident_stop_voice(trident_t * 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(trident_t * 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)); +} + +/*--------------------------------------------------------------------------- + int snd_trident_allocate_pcm_channel(trident_t *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(trident_t * 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(trident_t *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(trident_t * 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(trident_t *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. + + Paramters: trident - pointer to target device class for 4DWave. + voice - synthesizer voice structure + Each register field. + + ---------------------------------------------------------------------------*/ + +void snd_trident_write_voice_regs(trident_t * trident, + snd_trident_voice_t * 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(); + } + + 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 +} + +/*--------------------------------------------------------------------------- + snd_trident_write_cso_reg + + Description: This routine will write the new CSO offset + register to hardware. + + Paramters: trident - pointer to target device class for 4DWave. + voice - synthesizer voice structure + CSO - new CSO value + + ---------------------------------------------------------------------------*/ + +static void snd_trident_write_cso_reg(trident_t * trident, snd_trident_voice_t * 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. + + Paramters: trident - pointer to target device class for 4DWave. + voice - synthesizer voice structure + ESO - new ESO value + + ---------------------------------------------------------------------------*/ + +static void snd_trident_write_eso_reg(trident_t * trident, snd_trident_voice_t * 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. + + Paramters: trident - pointer to target device class for 4DWave. + voice - synthesizer voice structure + Vol - new voice volume + + ---------------------------------------------------------------------------*/ + +static void snd_trident_write_vol_reg(trident_t * trident, snd_trident_voice_t * 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. + + Paramters: trident - pointer to target device class for 4DWave. + voice - synthesizer voice structure + Pan - new pan value + + ---------------------------------------------------------------------------*/ + +static void snd_trident_write_pan_reg(trident_t * trident, snd_trident_voice_t * 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. + + Paramters: trident - pointer to target device class for 4DWave. + voice - synthesizer voice structure + RVol - new reverb volume + + ---------------------------------------------------------------------------*/ + +static void snd_trident_write_rvol_reg(trident_t * trident, snd_trident_voice_t * 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. + + Paramters: trident - pointer to target device class for 4DWave. + voice - synthesizer voice structure + CVol - new chorus volume + + ---------------------------------------------------------------------------*/ + +static void snd_trident_write_cvol_reg(trident_t * trident, snd_trident_voice_t * 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. + + Paramters: 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. + + Paramters: 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. + + Paramters: 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. + + Paramters: trident - pointer to target device class for 4DWave. + substream - PCM substream + + Returns: Control value. + + ---------------------------------------------------------------------------*/ +static unsigned int snd_trident_control_mode(snd_pcm_substream_t *substream) +{ + unsigned int CTRL; + snd_pcm_runtime_t *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. + + Paramters: substream - PCM substream class + cmd - what ioctl message to process + arg - additional message infoarg + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_ioctl(snd_pcm_substream_t * 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(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + snd_trident_voice_t *voice = (snd_trident_voice_t *) 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(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data; + snd_trident_voice_t *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(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * 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(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data; + snd_trident_voice_t *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(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data; + snd_trident_voice_t *evoice = voice->extra; + snd_trident_pcm_mixer_t *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(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * 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(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + snd_trident_voice_t *voice = (snd_trident_voice_t *) 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(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * 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(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data; + snd_trident_voice_t *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(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data; + snd_trident_voice_t *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(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data; + snd_trident_voice_t *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(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + trident_t *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(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data; + snd_trident_voice_t *evoice = voice->extra; + snd_trident_pcm_mixer_t *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(snd_pcm_substream_t *substream, + int cmd) + +{ + trident_t *trident = snd_pcm_substream_chip(substream); + struct list_head *pos; + snd_pcm_substream_t *s; + unsigned int what, whati, capture_flag, spdif_flag; + snd_trident_voice_t *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(pos, substream) { + s = snd_pcm_group_substream_entry(pos); + if ((trident_t *) snd_pcm_substream_chip(s) == trident) { + voice = (snd_trident_voice_t *) 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)); + outb(trident->spdif_pcm_ctrl, 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(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + snd_trident_voice_t *voice = (snd_trident_voice_t *) 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 + + Paramters: pcm1 - PCM device class + + Returns: position of buffer + + ---------------------------------------------------------------------------*/ + +static snd_pcm_uframes_t snd_trident_capture_pointer(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + snd_trident_voice_t *voice = (snd_trident_voice_t *) 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(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + snd_trident_voice_t *voice = (snd_trident_voice_t *) 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 snd_pcm_hardware_t 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 snd_pcm_hardware_t 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 snd_pcm_hardware_t 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 snd_pcm_hardware_t 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 snd_pcm_hardware_t 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(snd_pcm_runtime_t *runtime) +{ + snd_trident_voice_t *voice = (snd_trident_voice_t *) runtime->private_data; + trident_t *trident; + + if (voice) { + trident = voice->trident; + snd_trident_free_voice(trident, voice); + } +} + +static int snd_trident_playback_open(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + snd_trident_voice_t *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(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + snd_trident_voice_t *voice = (snd_trident_voice_t *) 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(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_trident_voice_t *voice; + snd_pcm_runtime_t *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(snd_pcm_substream_t * substream) +{ + trident_t *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(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_trident_voice_t *voice; + snd_pcm_runtime_t *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(snd_pcm_substream_t * 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(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_trident_voice_t *voice; + snd_pcm_runtime_t *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(snd_pcm_substream_t * substream) +{ + trident_t *trident = snd_pcm_substream_chip(substream); + snd_trident_voice_t *voice; + snd_pcm_runtime_t *runtime = substream->runtime; + voice = (snd_trident_voice_t *) 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 snd_pcm_ops_t 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 snd_pcm_ops_t 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 snd_pcm_ops_t 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 snd_pcm_ops_t 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 snd_pcm_ops_t 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 snd_pcm_ops_t 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 snd_pcm_ops_t 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 snd_pcm_ops_t 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_free + + Description: This routine release the 4DWave private data. + + Paramters: private_data - pointer to 4DWave device info. + + Returns: None + + ---------------------------------------------------------------------------*/ +static void snd_trident_pcm_free(snd_pcm_t *pcm) +{ + trident_t *trident = pcm->private_data; + trident->pcm = NULL; + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static void snd_trident_foldback_pcm_free(snd_pcm_t *pcm) +{ + trident_t *trident = pcm->private_data; + trident->foldback = NULL; + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static void snd_trident_spdif_pcm_free(snd_pcm_t *pcm) +{ + trident_t *trident = pcm->private_data; + trident->spdif = NULL; + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +/*--------------------------------------------------------------------------- + snd_trident_pcm + + Description: This routine registers the 4DWave device for PCM support. + + Paramters: trident - pointer to target device class for 4DWave. + + Returns: None + + ---------------------------------------------------------------------------*/ + +int __devinit snd_trident_pcm(trident_t * trident, int device, snd_pcm_t ** rpcm) +{ + snd_pcm_t *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; + pcm->private_free = snd_trident_pcm_free; + + 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) { + snd_pcm_substream_t *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. + + Paramters: trident - pointer to target device class for 4DWave. + + Returns: None + + ---------------------------------------------------------------------------*/ + +int __devinit snd_trident_foldback_pcm(trident_t * trident, int device, snd_pcm_t ** rpcm) +{ + snd_pcm_t *foldback; + int err; + int num_chan = 3; + snd_pcm_substream_t *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; + foldback->private_free = snd_trident_foldback_pcm_free; + 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. + + Paramters: trident - pointer to target device class for 4DWave-NX. + + Returns: None + + ---------------------------------------------------------------------------*/ + +int __devinit snd_trident_spdif_pcm(trident_t * trident, int device, snd_pcm_t ** rpcm) +{ + snd_pcm_t *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; + spdif->private_free = snd_trident_spdif_pcm_free; + 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 + ---------------------------------------------------------------------------*/ + +static int snd_trident_spdif_control_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_trident_spdif_control_get(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *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(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *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 snd_kcontrol_new_t 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(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_trident_spdif_default_get(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *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(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *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 snd_kcontrol_new_t 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(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_trident_spdif_mask_get(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * 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 snd_kcontrol_new_t 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(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_trident_spdif_stream_get(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *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(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *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 snd_kcontrol_new_t 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 + ---------------------------------------------------------------------------*/ + +static int snd_trident_ac97_control_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_trident_ac97_control_get(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *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(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *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 snd_kcontrol_new_t 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(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * 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(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *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 int snd_trident_vol_control_put(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *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 snd_kcontrol_new_t 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, +}; + +static snd_kcontrol_new_t 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, +}; + +/*--------------------------------------------------------------------------- + snd_trident_pcm_vol_control + + Description: PCM front volume control + ---------------------------------------------------------------------------*/ + +static int snd_trident_pcm_vol_control_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + trident_t *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(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *trident = snd_kcontrol_chip(kcontrol); + snd_trident_pcm_mixer_t *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(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *trident = snd_kcontrol_chip(kcontrol); + snd_trident_pcm_mixer_t *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 snd_kcontrol_new_t 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, +}; + +/*--------------------------------------------------------------------------- + snd_trident_pcm_pan_control + + Description: PCM front pan control + ---------------------------------------------------------------------------*/ + +static int snd_trident_pcm_pan_control_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * 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(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *trident = snd_kcontrol_chip(kcontrol); + snd_trident_pcm_mixer_t *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(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *trident = snd_kcontrol_chip(kcontrol); + snd_trident_pcm_mixer_t *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 snd_kcontrol_new_t 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(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * 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(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *trident = snd_kcontrol_chip(kcontrol); + snd_trident_pcm_mixer_t *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(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *trident = snd_kcontrol_chip(kcontrol); + snd_trident_pcm_mixer_t *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 snd_kcontrol_new_t 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, +}; + +/*--------------------------------------------------------------------------- + snd_trident_pcm_cvol_control + + Description: PCM chorus volume control + ---------------------------------------------------------------------------*/ + +static int snd_trident_pcm_cvol_control_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * 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(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *trident = snd_kcontrol_chip(kcontrol); + snd_trident_pcm_mixer_t *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(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + trident_t *trident = snd_kcontrol_chip(kcontrol); + snd_trident_pcm_mixer_t *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 snd_kcontrol_new_t 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, +}; + +static void snd_trident_notify_pcm_change1(snd_card_t * card, snd_kcontrol_t *kctl, int num, int activate) +{ + snd_ctl_elem_id_t id; + + snd_runtime_check(kctl != NULL, 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(trident_t *trident, snd_trident_pcm_mixer_t *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(trident_t *trident, snd_trident_voice_t *voice, snd_pcm_substream_t *substream) +{ + snd_trident_pcm_mixer_t *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(trident_t *trident, snd_trident_voice_t *voice, snd_pcm_substream_t *substream) +{ + snd_trident_pcm_mixer_t *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. + + Paramters: trident - pointer to target device class for 4DWave. + + Returns: None + + ---------------------------------------------------------------------------*/ + +static int __devinit snd_trident_mixer(trident_t * trident, int pcm_spdif_device) +{ + ac97_template_t _ac97; + snd_card_t * card = trident->card; + snd_kcontrol_t *kctl; + snd_ctl_elem_value_t *uctl; + int idx, err, retries = 2; + static ac97_bus_ops_t ops = { + .write = snd_trident_codec_write, + .read = snd_trident_codec_read, + }; + + uctl = kcalloc(1, 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("SI7018: the secondary codec - invalid access\n"); +#if 0 // only for my testing purpose --jk + { + ac97_t *mc97; + err = snd_ac97_modem(trident->card, &_ac97, &mc97); + if (err < 0) + snd_printk("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++) { + snd_trident_pcm_mixer_t *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) +{ + trident_t *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) +{ + trident_t *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) +{ + trident_t *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) +{ + trident_t *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)); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1 + 20 * HZ / 1000); /* 20msec */ + return 0; + case GAMEPORT_MODE_RAW: + outb(0, TRID_REG(chip, GAMEPORT_GCR)); + return 0; + default: + return -1; + } +} + +int __devinit snd_trident_create_gameport(trident_t *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(trident_t *chip) +{ + if (chip->gameport) { + gameport_unregister_port(chip->gameport); + chip->gameport = NULL; + } +} +#else +int __devinit snd_trident_create_gameport(trident_t *chip) { return -ENOSYS; } +static inline void snd_trident_free_gameport(trident_t *chip) { } +#endif /* CONFIG_GAMEPORT */ + +/* + * delay for 1 tick + */ +inline static void do_delay(trident_t *chip) +{ + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); +} + +/* + * SiS reset routine + */ + +static int snd_trident_sis_reset(trident_t *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("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(snd_info_entry_t *entry, + snd_info_buffer_t * buffer) +{ + trident_t *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)); + } + } +#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)) + snd_iprintf(buffer,"\nWavetable Synth\n"); + snd_iprintf(buffer, "Memory Maximum : %d\n", trident->synth.max_size); + snd_iprintf(buffer, "Memory Used : %d\n", trident->synth.current_size); + snd_iprintf(buffer, "Memory Free : %d\n", (trident->synth.max_size-trident->synth.current_size)); +#endif +} + +static void __devinit snd_trident_proc_init(trident_t * trident) +{ + snd_info_entry_t *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, 1024, snd_trident_proc_read); +} + +static int snd_trident_dev_free(snd_device_t *device) +{ + trident_t *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). + + Paramters: trident - pointer to target device class for 4DWave. + + Returns: 0 or negative error code + + ---------------------------------------------------------------------------*/ + +static int __devinit snd_trident_tlb_alloc(trident_t *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*)(((unsigned long)trident->tlb.buffer.area + SNDRV_TRIDENT_MAX_PAGES * 4 - 1) & ~(SNDRV_TRIDENT_MAX_PAGES * 4 - 1)); + trident->tlb.entries_dmaaddr = (trident->tlb.buffer.addr + SNDRV_TRIDENT_MAX_PAGES * 4 - 1) & ~(SNDRV_TRIDENT_MAX_PAGES * 4 - 1); + /* allocate shadow TLB page table (virtual addresses) */ + trident->tlb.shadow_entries = (unsigned long *)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(snd_trident_memblk_arg_t); + return 0; +} + +/* + * initialize 4D DX chip + */ + +static void snd_trident_stop_all_voices(trident_t *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(trident_t *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(trident_t *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(trident_t *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. + + Paramters: 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(snd_card_t * card, + struct pci_dev *pci, + int pcm_streams, + int pcm_spdif_device, + int max_wavetable_size, + trident_t ** rtrident) +{ + trident_t *trident; + int i, err; + snd_trident_voice_t *voice; + snd_trident_pcm_mixer_t *tmix; + static snd_device_ops_t 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, 0x3fffffff) < 0 || + pci_set_consistent_dma_mask(pci, 0x3fffffff) < 0) { + snd_printk("architecture does not support 30bit PCI busmaster DMA\n"); + pci_disable_device(pci); + return -ENXIO; + } + + trident = kcalloc(1, 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, SA_INTERRUPT|SA_SHIRQ, "Trident Audio", (void *) trident)) { + snd_printk("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_card_set_pm_callback(card, snd_trident_suspend, snd_trident_resume, 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. + + Paramters: trident - device specific private data for 4DWave card + + Returns: None. + + ---------------------------------------------------------------------------*/ + +static int snd_trident_free(trident_t *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->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); + } + if (trident->irq >= 0) + free_irq(trident->irq, (void *)trident); + pci_release_regions(trident->pci); + pci_disable_device(trident->pci); + kfree(trident); + return 0; +} + +/*--------------------------------------------------------------------------- + snd_trident_interrupt + + Description: ISR for Trident 4DWave device + + Paramters: 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. + + ---------------------------------------------------------------------------*/ + +static irqreturn_t snd_trident_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + trident_t *trident = dev_id; + unsigned int audio_int, chn_int, stimer, channel, mask, tmp; + int delta; + snd_trident_voice_t *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) { + snd_mpu401_uart_interrupt(irq, trident->rmidi->private_data, regs); + } else { + inb(TRID_REG(trident, T4D_MPUR0)); + } + } + // outl((ST_TARGET_REACHED | MIXER_OVERFLOW | MIXER_UNDERFLOW), TRID_REG(trident, T4D_MISCINT)); + return IRQ_HANDLED; +} + +/*--------------------------------------------------------------------------- + snd_trident_attach_synthesizer + + Description: Attach synthesizer hooks + + Paramters: trident - device specific private data for 4DWave card + + Returns: None. + + ---------------------------------------------------------------------------*/ +int snd_trident_attach_synthesizer(trident_t *trident) +{ +#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)) + if (snd_seq_device_new(trident->card, 1, SNDRV_SEQ_DEV_ID_TRIDENT, + sizeof(trident_t*), &trident->seq_dev) >= 0) { + strcpy(trident->seq_dev->name, "4DWave"); + *(trident_t**)SNDRV_SEQ_DEVICE_ARGPTR(trident->seq_dev) = trident; + } +#endif + return 0; +} + +snd_trident_voice_t *snd_trident_alloc_voice(trident_t * trident, int type, int client, int port) +{ + snd_trident_voice_t *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; +} + +void snd_trident_free_voice(trident_t * trident, snd_trident_voice_t *voice) +{ + unsigned long flags; + void (*private_free)(snd_trident_voice_t *); + 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); +} + +static void snd_trident_clear_voices(trident_t * 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 +static int snd_trident_suspend(snd_card_t *card, pm_message_t state) +{ + trident_t *trident = card->pm_private_data; + + trident->in_suspend = 1; + snd_pcm_suspend_all(trident->pcm); + if (trident->foldback) + snd_pcm_suspend_all(trident->foldback); + if (trident->spdif) + snd_pcm_suspend_all(trident->spdif); + + snd_ac97_suspend(trident->ac97); + if (trident->ac97_sec) + snd_ac97_suspend(trident->ac97_sec); + + switch (trident->device) { + case TRIDENT_DEVICE_ID_DX: + case TRIDENT_DEVICE_ID_NX: + break; /* TODO */ + case TRIDENT_DEVICE_ID_SI7018: + break; + } + pci_disable_device(trident->pci); + return 0; +} + +static int snd_trident_resume(snd_card_t *card) +{ + trident_t *trident = card->pm_private_data; + + pci_enable_device(trident->pci); + if (pci_set_dma_mask(trident->pci, 0x3fffffff) < 0 || + pci_set_consistent_dma_mask(trident->pci, 0x3fffffff) < 0) + snd_printk(KERN_WARNING "trident: can't set the proper DMA mask\n"); + pci_set_master(trident->pci); /* to be sure */ + + 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); + if (trident->ac97_sec) + snd_ac97_resume(trident->ac97_sec); + + /* restore some registers */ + outl(trident->musicvol_wavevol, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL)); + + snd_trident_enable_eso(trident); + + trident->in_suspend = 0; + return 0; +} +#endif /* CONFIG_PM */ + +EXPORT_SYMBOL(snd_trident_alloc_voice); +EXPORT_SYMBOL(snd_trident_free_voice); +EXPORT_SYMBOL(snd_trident_start_voice); +EXPORT_SYMBOL(snd_trident_stop_voice); +EXPORT_SYMBOL(snd_trident_write_voice_regs); +/* trident_memory.c symbols */ +EXPORT_SYMBOL(snd_trident_synth_alloc); +EXPORT_SYMBOL(snd_trident_synth_free); +EXPORT_SYMBOL(snd_trident_synth_copy_from_user); diff --git a/sound/pci/trident/trident_memory.c b/sound/pci/trident/trident_memory.c new file mode 100644 index 00000000000..6cc282681e0 --- /dev/null +++ b/sound/pci/trident/trident_memory.c @@ -0,0 +1,476 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * Copyright (c) by Scott McNab <sdm@fractalgraphics.com.au> + * + * Trident 4DWave-NX memory page allocation (TLB area) + * Trident chip can handle only 16MByte of the memory at the same time. + * + * + * 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 + * + */ + +#include <sound/driver.h> +#include <asm/io.h> +#include <linux/pci.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/trident.h> + +/* page arguments of these two macros are Trident page (4096 bytes), not like + * aligned pages in others + */ +#define __set_tlb_bus(trident,page,ptr,addr) \ + do { (trident)->tlb.entries[page] = cpu_to_le32((addr) & ~(SNDRV_TRIDENT_PAGE_SIZE-1)); \ + (trident)->tlb.shadow_entries[page] = (ptr); } while (0) +#define __tlb_to_ptr(trident,page) \ + (void*)((trident)->tlb.shadow_entries[page]) +#define __tlb_to_addr(trident,page) \ + (dma_addr_t)le32_to_cpu((trident->tlb.entries[page]) & ~(SNDRV_TRIDENT_PAGE_SIZE - 1)) + +#if PAGE_SIZE == 4096 +/* page size == SNDRV_TRIDENT_PAGE_SIZE */ +#define ALIGN_PAGE_SIZE PAGE_SIZE /* minimum page size for allocation */ +#define MAX_ALIGN_PAGES SNDRV_TRIDENT_MAX_PAGES /* maxmium aligned pages */ +/* fill TLB entrie(s) corresponding to page with ptr */ +#define set_tlb_bus(trident,page,ptr,addr) __set_tlb_bus(trident,page,ptr,addr) +/* fill TLB entrie(s) corresponding to page with silence pointer */ +#define set_silent_tlb(trident,page) __set_tlb_bus(trident, page, (unsigned long)trident->tlb.silent_page.area, trident->tlb.silent_page.addr) +/* get aligned page from offset address */ +#define get_aligned_page(offset) ((offset) >> 12) +/* get offset address from aligned page */ +#define aligned_page_offset(page) ((page) << 12) +/* get buffer address from aligned page */ +#define page_to_ptr(trident,page) __tlb_to_ptr(trident, page) +/* get PCI physical address from aligned page */ +#define page_to_addr(trident,page) __tlb_to_addr(trident, page) + +#elif PAGE_SIZE == 8192 +/* page size == SNDRV_TRIDENT_PAGE_SIZE x 2*/ +#define ALIGN_PAGE_SIZE PAGE_SIZE +#define MAX_ALIGN_PAGES (SNDRV_TRIDENT_MAX_PAGES / 2) +#define get_aligned_page(offset) ((offset) >> 13) +#define aligned_page_offset(page) ((page) << 13) +#define page_to_ptr(trident,page) __tlb_to_ptr(trident, (page) << 1) +#define page_to_addr(trident,page) __tlb_to_addr(trident, (page) << 1) + +/* fill TLB entries -- we need to fill two entries */ +static inline void set_tlb_bus(trident_t *trident, int page, unsigned long ptr, dma_addr_t addr) +{ + page <<= 1; + __set_tlb_bus(trident, page, ptr, addr); + __set_tlb_bus(trident, page+1, ptr + SNDRV_TRIDENT_PAGE_SIZE, addr + SNDRV_TRIDENT_PAGE_SIZE); +} +static inline void set_silent_tlb(trident_t *trident, int page) +{ + page <<= 1; + __set_tlb_bus(trident, page, (unsigned long)trident->tlb.silent_page.area, trident->tlb.silent_page.addr); + __set_tlb_bus(trident, page+1, (unsigned long)trident->tlb.silent_page.area, trident->tlb.silent_page.addr); +} + +#else +/* arbitrary size */ +#define UNIT_PAGES (PAGE_SIZE / SNDRV_TRIDENT_PAGE_SIZE) +#define ALIGN_PAGE_SIZE (SNDRV_TRIDENT_PAGE_SIZE * UNIT_PAGES) +#define MAX_ALIGN_PAGES (SNDRV_TRIDENT_MAX_PAGES / UNIT_PAGES) +/* Note: if alignment doesn't match to the maximum size, the last few blocks + * become unusable. To use such blocks, you'll need to check the validity + * of accessing page in set_tlb_bus and set_silent_tlb. search_empty() + * should also check it, too. + */ +#define get_aligned_page(offset) ((offset) / ALIGN_PAGE_SIZE) +#define aligned_page_offset(page) ((page) * ALIGN_PAGE_SIZE) +#define page_to_ptr(trident,page) __tlb_to_ptr(trident, (page) * UNIT_PAGES) +#define page_to_addr(trident,page) __tlb_to_addr(trident, (page) * UNIT_PAGES) + +/* fill TLB entries -- UNIT_PAGES entries must be filled */ +static inline void set_tlb_bus(trident_t *trident, int page, unsigned long ptr, dma_addr_t addr) +{ + int i; + page *= UNIT_PAGES; + for (i = 0; i < UNIT_PAGES; i++, page++) { + __set_tlb_bus(trident, page, ptr, addr); + ptr += SNDRV_TRIDENT_PAGE_SIZE; + addr += SNDRV_TRIDENT_PAGE_SIZE; + } +} +static inline void set_silent_tlb(trident_t *trident, int page) +{ + int i; + page *= UNIT_PAGES; + for (i = 0; i < UNIT_PAGES; i++, page++) + __set_tlb_bus(trident, page, (unsigned long)trident->tlb.silent_page.area, trident->tlb.silent_page.addr); +} + +#endif /* PAGE_SIZE */ + +/* calculate buffer pointer from offset address */ +inline static void *offset_ptr(trident_t *trident, int offset) +{ + char *ptr; + ptr = page_to_ptr(trident, get_aligned_page(offset)); + ptr += offset % ALIGN_PAGE_SIZE; + return (void*)ptr; +} + +/* first and last (aligned) pages of memory block */ +#define firstpg(blk) (((snd_trident_memblk_arg_t*)snd_util_memblk_argptr(blk))->first_page) +#define lastpg(blk) (((snd_trident_memblk_arg_t*)snd_util_memblk_argptr(blk))->last_page) + +/* + * search empty pages which may contain given size + */ +static snd_util_memblk_t * +search_empty(snd_util_memhdr_t *hdr, int size) +{ + snd_util_memblk_t *blk, *prev; + int page, psize; + struct list_head *p; + + psize = get_aligned_page(size + ALIGN_PAGE_SIZE -1); + prev = NULL; + page = 0; + list_for_each(p, &hdr->block) { + blk = list_entry(p, snd_util_memblk_t, list); + if (page + psize <= firstpg(blk)) + goto __found_pages; + page = lastpg(blk) + 1; + } + if (page + psize > MAX_ALIGN_PAGES) + return NULL; + +__found_pages: + /* create a new memory block */ + blk = __snd_util_memblk_new(hdr, psize * ALIGN_PAGE_SIZE, p->prev); + if (blk == NULL) + return NULL; + blk->offset = aligned_page_offset(page); /* set aligned offset */ + firstpg(blk) = page; + lastpg(blk) = page + psize - 1; + return blk; +} + + +/* + * check if the given pointer is valid for pages + */ +static int is_valid_page(unsigned long ptr) +{ + if (ptr & ~0x3fffffffUL) { + snd_printk("max memory size is 1GB!!\n"); + return 0; + } + if (ptr & (SNDRV_TRIDENT_PAGE_SIZE-1)) { + snd_printk("page is not aligned\n"); + return 0; + } + return 1; +} + +/* + * page allocation for DMA (Scatter-Gather version) + */ +static snd_util_memblk_t * +snd_trident_alloc_sg_pages(trident_t *trident, snd_pcm_substream_t *substream) +{ + snd_util_memhdr_t *hdr; + snd_util_memblk_t *blk; + snd_pcm_runtime_t *runtime = substream->runtime; + int idx, page; + struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream); + + snd_assert(runtime->dma_bytes > 0 && runtime->dma_bytes <= SNDRV_TRIDENT_MAX_PAGES * SNDRV_TRIDENT_PAGE_SIZE, return NULL); + hdr = trident->tlb.memhdr; + snd_assert(hdr != NULL, return NULL); + + + + down(&hdr->block_mutex); + blk = search_empty(hdr, runtime->dma_bytes); + if (blk == NULL) { + up(&hdr->block_mutex); + return NULL; + } + if (lastpg(blk) - firstpg(blk) >= sgbuf->pages) { + snd_printk(KERN_ERR "page calculation doesn't match: allocated pages = %d, trident = %d/%d\n", sgbuf->pages, firstpg(blk), lastpg(blk)); + __snd_util_mem_free(hdr, blk); + up(&hdr->block_mutex); + return NULL; + } + + /* set TLB entries */ + idx = 0; + for (page = firstpg(blk); page <= lastpg(blk); page++, idx++) { + dma_addr_t addr = sgbuf->table[idx].addr; + unsigned long ptr = (unsigned long)sgbuf->table[idx].buf; + if (! is_valid_page(addr)) { + __snd_util_mem_free(hdr, blk); + up(&hdr->block_mutex); + return NULL; + } + set_tlb_bus(trident, page, ptr, addr); + } + up(&hdr->block_mutex); + return blk; +} + +/* + * page allocation for DMA (contiguous version) + */ +static snd_util_memblk_t * +snd_trident_alloc_cont_pages(trident_t *trident, snd_pcm_substream_t *substream) +{ + snd_util_memhdr_t *hdr; + snd_util_memblk_t *blk; + int page; + snd_pcm_runtime_t *runtime = substream->runtime; + dma_addr_t addr; + unsigned long ptr; + + snd_assert(runtime->dma_bytes> 0 && runtime->dma_bytes <= SNDRV_TRIDENT_MAX_PAGES * SNDRV_TRIDENT_PAGE_SIZE, return NULL); + hdr = trident->tlb.memhdr; + snd_assert(hdr != NULL, return NULL); + + down(&hdr->block_mutex); + blk = search_empty(hdr, runtime->dma_bytes); + if (blk == NULL) { + up(&hdr->block_mutex); + return NULL; + } + + /* set TLB entries */ + addr = runtime->dma_addr; + ptr = (unsigned long)runtime->dma_area; + for (page = firstpg(blk); page <= lastpg(blk); page++, + ptr += SNDRV_TRIDENT_PAGE_SIZE, addr += SNDRV_TRIDENT_PAGE_SIZE) { + if (! is_valid_page(addr)) { + __snd_util_mem_free(hdr, blk); + up(&hdr->block_mutex); + return NULL; + } + set_tlb_bus(trident, page, ptr, addr); + } + up(&hdr->block_mutex); + return blk; +} + +/* + * page allocation for DMA + */ +snd_util_memblk_t * +snd_trident_alloc_pages(trident_t *trident, snd_pcm_substream_t *substream) +{ + snd_assert(trident != NULL, return NULL); + snd_assert(substream != NULL, return NULL); + if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV_SG) + return snd_trident_alloc_sg_pages(trident, substream); + else + return snd_trident_alloc_cont_pages(trident, substream); +} + + +/* + * release DMA buffer from page table + */ +int snd_trident_free_pages(trident_t *trident, snd_util_memblk_t *blk) +{ + snd_util_memhdr_t *hdr; + int page; + + snd_assert(trident != NULL, return -EINVAL); + snd_assert(blk != NULL, return -EINVAL); + + hdr = trident->tlb.memhdr; + down(&hdr->block_mutex); + /* reset TLB entries */ + for (page = firstpg(blk); page <= lastpg(blk); page++) + set_silent_tlb(trident, page); + /* free memory block */ + __snd_util_mem_free(hdr, blk); + up(&hdr->block_mutex); + return 0; +} + + +/*---------------------------------------------------------------- + * memory allocation using multiple pages (for synth) + *---------------------------------------------------------------- + * Unlike the DMA allocation above, non-contiguous pages are + * assigned to TLB. + *----------------------------------------------------------------*/ + +/* + */ +static int synth_alloc_pages(trident_t *hw, snd_util_memblk_t *blk); +static int synth_free_pages(trident_t *hw, snd_util_memblk_t *blk); + +/* + * allocate a synth sample area + */ +snd_util_memblk_t * +snd_trident_synth_alloc(trident_t *hw, unsigned int size) +{ + snd_util_memblk_t *blk; + snd_util_memhdr_t *hdr = hw->tlb.memhdr; + + down(&hdr->block_mutex); + blk = __snd_util_mem_alloc(hdr, size); + if (blk == NULL) { + up(&hdr->block_mutex); + return NULL; + } + if (synth_alloc_pages(hw, blk)) { + __snd_util_mem_free(hdr, blk); + up(&hdr->block_mutex); + return NULL; + } + up(&hdr->block_mutex); + return blk; +} + + +/* + * free a synth sample area + */ +int +snd_trident_synth_free(trident_t *hw, snd_util_memblk_t *blk) +{ + snd_util_memhdr_t *hdr = hw->tlb.memhdr; + + down(&hdr->block_mutex); + synth_free_pages(hw, blk); + __snd_util_mem_free(hdr, blk); + up(&hdr->block_mutex); + return 0; +} + + +/* + * reset TLB entry and free kernel page + */ +static void clear_tlb(trident_t *trident, int page) +{ + void *ptr = page_to_ptr(trident, page); + dma_addr_t addr = page_to_addr(trident, page); + set_silent_tlb(trident, page); + if (ptr) { + struct snd_dma_buffer dmab; + dmab.dev.type = SNDRV_DMA_TYPE_DEV; + dmab.dev.dev = snd_dma_pci_data(trident->pci); + dmab.area = ptr; + dmab.addr = addr; + dmab.bytes = ALIGN_PAGE_SIZE; + snd_dma_free_pages(&dmab); + } +} + +/* check new allocation range */ +static void get_single_page_range(snd_util_memhdr_t *hdr, snd_util_memblk_t *blk, int *first_page_ret, int *last_page_ret) +{ + struct list_head *p; + snd_util_memblk_t *q; + int first_page, last_page; + first_page = firstpg(blk); + if ((p = blk->list.prev) != &hdr->block) { + q = list_entry(p, snd_util_memblk_t, list); + if (lastpg(q) == first_page) + first_page++; /* first page was already allocated */ + } + last_page = lastpg(blk); + if ((p = blk->list.next) != &hdr->block) { + q = list_entry(p, snd_util_memblk_t, list); + if (firstpg(q) == last_page) + last_page--; /* last page was already allocated */ + } + *first_page_ret = first_page; + *last_page_ret = last_page; +} + +/* + * allocate kernel pages and assign them to TLB + */ +static int synth_alloc_pages(trident_t *hw, snd_util_memblk_t *blk) +{ + int page, first_page, last_page; + struct snd_dma_buffer dmab; + + firstpg(blk) = get_aligned_page(blk->offset); + lastpg(blk) = get_aligned_page(blk->offset + blk->size - 1); + get_single_page_range(hw->tlb.memhdr, blk, &first_page, &last_page); + + /* allocate a kernel page for each Trident page - + * fortunately Trident page size and kernel PAGE_SIZE is identical! + */ + for (page = first_page; page <= last_page; page++) { + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(hw->pci), + ALIGN_PAGE_SIZE, &dmab) < 0) + goto __fail; + if (! is_valid_page(dmab.addr)) { + snd_dma_free_pages(&dmab); + goto __fail; + } + set_tlb_bus(hw, page, (unsigned long)dmab.area, dmab.addr); + } + return 0; + +__fail: + /* release allocated pages */ + last_page = page - 1; + for (page = first_page; page <= last_page; page++) + clear_tlb(hw, page); + + return -ENOMEM; +} + +/* + * free pages + */ +static int synth_free_pages(trident_t *trident, snd_util_memblk_t *blk) +{ + int page, first_page, last_page; + + get_single_page_range(trident->tlb.memhdr, blk, &first_page, &last_page); + for (page = first_page; page <= last_page; page++) + clear_tlb(trident, page); + + return 0; +} + +/* + * copy_from_user(blk + offset, data, size) + */ +int snd_trident_synth_copy_from_user(trident_t *trident, snd_util_memblk_t *blk, int offset, const char __user *data, int size) +{ + int page, nextofs, end_offset, temp, temp1; + + offset += blk->offset; + end_offset = offset + size; + page = get_aligned_page(offset) + 1; + do { + nextofs = aligned_page_offset(page); + temp = nextofs - offset; + temp1 = end_offset - offset; + if (temp1 < temp) + temp = temp1; + if (copy_from_user(offset_ptr(trident, offset), data, temp)) + return -EFAULT; + offset = nextofs; + data += temp; + page++; + } while (offset < end_offset); + return 0; +} + diff --git a/sound/pci/trident/trident_synth.c b/sound/pci/trident/trident_synth.c new file mode 100644 index 00000000000..5d5a719b058 --- /dev/null +++ b/sound/pci/trident/trident_synth.c @@ -0,0 +1,1031 @@ +/* + * Routines for Trident 4DWave NX/DX soundcards - Synthesizer + * Copyright (c) by Scott McNab <jedi@tartarus.uwa.edu.au> + * + * + * 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 + * + */ + +#include <sound/driver.h> +#include <asm/io.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <sound/core.h> +#include <sound/trident.h> +#include <sound/seq_device.h> + +MODULE_AUTHOR("Scott McNab <jedi@tartarus.uwa.edu.au>"); +MODULE_DESCRIPTION("Routines for Trident 4DWave NX/DX soundcards - Synthesizer"); +MODULE_LICENSE("GPL"); + +/* linear to log pan conversion table (4.2 channel attenuation format) */ +static unsigned int pan_table[63] = { + 7959, 7733, 7514, 7301, 7093, 6892, 6697, 6507, + 6322, 6143, 5968, 5799, 5634, 5475, 5319, 5168, + 5022, 4879, 4741, 4606, 4475, 4349, 4225, 4105, + 3989, 3876, 3766, 3659, 3555, 3454, 3356, 3261, + 3168, 3078, 2991, 2906, 2824, 2744, 2666, 2590, + 2517, 2445, 2376, 2308, 2243, 2179, 2117, 2057, + 1999, 1942, 1887, 1833, 1781, 1731, 1682, 1634, + 1588, 1543, 1499, 1456, 1415, 1375, 1336 +}; + +#define LOG_TABLE_SIZE 386 + +/* Linear half-attenuation to log conversion table in the format: + * {linear volume, logarithmic attenuation equivalent}, ... + * + * Provides conversion from a linear half-volume value in the range + * [0,8192] to a logarithmic attenuation value in the range 0 to 6.02dB. + * Halving the linear volume is equivalent to an additional 6dB of + * logarithmic attenuation. The algorithm used in log_from_linear() + * therefore uses this table as follows: + * + * - loop and for every time the volume is less than half the maximum + * volume (16384), add another 6dB and halve the maximum value used + * for this comparison. + * - when the volume is greater than half the maximum volume, take + * the difference of the volume to half volume (in the range [0,8192]) + * and look up the log_table[] to find the nearest entry. + * - take the logarithic component of this entry and add it to the + * resulting attenuation. + * + * Thus this routine provides a linear->log conversion for a range of + * [0,16384] using only 386 table entries + * + * Note: although this table stores log attenuation in 8.8 format, values + * were only calculated for 6 bits fractional precision, since that is + * the most precision offered by the trident hardware. + */ + +static unsigned short log_table[LOG_TABLE_SIZE*2] = +{ + 4, 0x0604, 19, 0x0600, 34, 0x05fc, + 49, 0x05f8, 63, 0x05f4, 78, 0x05f0, 93, 0x05ec, 108, 0x05e8, + 123, 0x05e4, 138, 0x05e0, 153, 0x05dc, 168, 0x05d8, 183, 0x05d4, + 198, 0x05d0, 213, 0x05cc, 228, 0x05c8, 244, 0x05c4, 259, 0x05c0, + 274, 0x05bc, 289, 0x05b8, 304, 0x05b4, 320, 0x05b0, 335, 0x05ac, + 350, 0x05a8, 366, 0x05a4, 381, 0x05a0, 397, 0x059c, 412, 0x0598, + 428, 0x0594, 443, 0x0590, 459, 0x058c, 474, 0x0588, 490, 0x0584, + 506, 0x0580, 521, 0x057c, 537, 0x0578, 553, 0x0574, 568, 0x0570, + 584, 0x056c, 600, 0x0568, 616, 0x0564, 632, 0x0560, 647, 0x055c, + 663, 0x0558, 679, 0x0554, 695, 0x0550, 711, 0x054c, 727, 0x0548, + 743, 0x0544, 759, 0x0540, 776, 0x053c, 792, 0x0538, 808, 0x0534, + 824, 0x0530, 840, 0x052c, 857, 0x0528, 873, 0x0524, 889, 0x0520, + 906, 0x051c, 922, 0x0518, 938, 0x0514, 955, 0x0510, 971, 0x050c, + 988, 0x0508, 1004, 0x0504, 1021, 0x0500, 1037, 0x04fc, 1054, 0x04f8, + 1071, 0x04f4, 1087, 0x04f0, 1104, 0x04ec, 1121, 0x04e8, 1138, 0x04e4, + 1154, 0x04e0, 1171, 0x04dc, 1188, 0x04d8, 1205, 0x04d4, 1222, 0x04d0, + 1239, 0x04cc, 1256, 0x04c8, 1273, 0x04c4, 1290, 0x04c0, 1307, 0x04bc, + 1324, 0x04b8, 1341, 0x04b4, 1358, 0x04b0, 1376, 0x04ac, 1393, 0x04a8, + 1410, 0x04a4, 1427, 0x04a0, 1445, 0x049c, 1462, 0x0498, 1479, 0x0494, + 1497, 0x0490, 1514, 0x048c, 1532, 0x0488, 1549, 0x0484, 1567, 0x0480, + 1584, 0x047c, 1602, 0x0478, 1620, 0x0474, 1637, 0x0470, 1655, 0x046c, + 1673, 0x0468, 1690, 0x0464, 1708, 0x0460, 1726, 0x045c, 1744, 0x0458, + 1762, 0x0454, 1780, 0x0450, 1798, 0x044c, 1816, 0x0448, 1834, 0x0444, + 1852, 0x0440, 1870, 0x043c, 1888, 0x0438, 1906, 0x0434, 1924, 0x0430, + 1943, 0x042c, 1961, 0x0428, 1979, 0x0424, 1997, 0x0420, 2016, 0x041c, + 2034, 0x0418, 2053, 0x0414, 2071, 0x0410, 2089, 0x040c, 2108, 0x0408, + 2127, 0x0404, 2145, 0x0400, 2164, 0x03fc, 2182, 0x03f8, 2201, 0x03f4, + 2220, 0x03f0, 2239, 0x03ec, 2257, 0x03e8, 2276, 0x03e4, 2295, 0x03e0, + 2314, 0x03dc, 2333, 0x03d8, 2352, 0x03d4, 2371, 0x03d0, 2390, 0x03cc, + 2409, 0x03c8, 2428, 0x03c4, 2447, 0x03c0, 2466, 0x03bc, 2485, 0x03b8, + 2505, 0x03b4, 2524, 0x03b0, 2543, 0x03ac, 2562, 0x03a8, 2582, 0x03a4, + 2601, 0x03a0, 2621, 0x039c, 2640, 0x0398, 2660, 0x0394, 2679, 0x0390, + 2699, 0x038c, 2718, 0x0388, 2738, 0x0384, 2758, 0x0380, 2777, 0x037c, + 2797, 0x0378, 2817, 0x0374, 2837, 0x0370, 2857, 0x036c, 2876, 0x0368, + 2896, 0x0364, 2916, 0x0360, 2936, 0x035c, 2956, 0x0358, 2976, 0x0354, + 2997, 0x0350, 3017, 0x034c, 3037, 0x0348, 3057, 0x0344, 3077, 0x0340, + 3098, 0x033c, 3118, 0x0338, 3138, 0x0334, 3159, 0x0330, 3179, 0x032c, + 3200, 0x0328, 3220, 0x0324, 3241, 0x0320, 3261, 0x031c, 3282, 0x0318, + 3303, 0x0314, 3323, 0x0310, 3344, 0x030c, 3365, 0x0308, 3386, 0x0304, + 3406, 0x0300, 3427, 0x02fc, 3448, 0x02f8, 3469, 0x02f4, 3490, 0x02f0, + 3511, 0x02ec, 3532, 0x02e8, 3553, 0x02e4, 3575, 0x02e0, 3596, 0x02dc, + 3617, 0x02d8, 3638, 0x02d4, 3660, 0x02d0, 3681, 0x02cc, 3702, 0x02c8, + 3724, 0x02c4, 3745, 0x02c0, 3767, 0x02bc, 3788, 0x02b8, 3810, 0x02b4, + 3831, 0x02b0, 3853, 0x02ac, 3875, 0x02a8, 3896, 0x02a4, 3918, 0x02a0, + 3940, 0x029c, 3962, 0x0298, 3984, 0x0294, 4006, 0x0290, 4028, 0x028c, + 4050, 0x0288, 4072, 0x0284, 4094, 0x0280, 4116, 0x027c, 4138, 0x0278, + 4160, 0x0274, 4182, 0x0270, 4205, 0x026c, 4227, 0x0268, 4249, 0x0264, + 4272, 0x0260, 4294, 0x025c, 4317, 0x0258, 4339, 0x0254, 4362, 0x0250, + 4384, 0x024c, 4407, 0x0248, 4430, 0x0244, 4453, 0x0240, 4475, 0x023c, + 4498, 0x0238, 4521, 0x0234, 4544, 0x0230, 4567, 0x022c, 4590, 0x0228, + 4613, 0x0224, 4636, 0x0220, 4659, 0x021c, 4682, 0x0218, 4705, 0x0214, + 4728, 0x0210, 4752, 0x020c, 4775, 0x0208, 4798, 0x0204, 4822, 0x0200, + 4845, 0x01fc, 4869, 0x01f8, 4892, 0x01f4, 4916, 0x01f0, 4939, 0x01ec, + 4963, 0x01e8, 4987, 0x01e4, 5010, 0x01e0, 5034, 0x01dc, 5058, 0x01d8, + 5082, 0x01d4, 5106, 0x01d0, 5130, 0x01cc, 5154, 0x01c8, 5178, 0x01c4, + 5202, 0x01c0, 5226, 0x01bc, 5250, 0x01b8, 5274, 0x01b4, 5299, 0x01b0, + 5323, 0x01ac, 5347, 0x01a8, 5372, 0x01a4, 5396, 0x01a0, 5420, 0x019c, + 5445, 0x0198, 5469, 0x0194, 5494, 0x0190, 5519, 0x018c, 5543, 0x0188, + 5568, 0x0184, 5593, 0x0180, 5618, 0x017c, 5643, 0x0178, 5668, 0x0174, + 5692, 0x0170, 5717, 0x016c, 5743, 0x0168, 5768, 0x0164, 5793, 0x0160, + 5818, 0x015c, 5843, 0x0158, 5868, 0x0154, 5894, 0x0150, 5919, 0x014c, + 5945, 0x0148, 5970, 0x0144, 5995, 0x0140, 6021, 0x013c, 6047, 0x0138, + 6072, 0x0134, 6098, 0x0130, 6124, 0x012c, 6149, 0x0128, 6175, 0x0124, + 6201, 0x0120, 6227, 0x011c, 6253, 0x0118, 6279, 0x0114, 6305, 0x0110, + 6331, 0x010c, 6357, 0x0108, 6384, 0x0104, 6410, 0x0100, 6436, 0x00fc, + 6462, 0x00f8, 6489, 0x00f4, 6515, 0x00f0, 6542, 0x00ec, 6568, 0x00e8, + 6595, 0x00e4, 6621, 0x00e0, 6648, 0x00dc, 6675, 0x00d8, 6702, 0x00d4, + 6728, 0x00d0, 6755, 0x00cc, 6782, 0x00c8, 6809, 0x00c4, 6836, 0x00c0, + 6863, 0x00bc, 6890, 0x00b8, 6917, 0x00b4, 6945, 0x00b0, 6972, 0x00ac, + 6999, 0x00a8, 7027, 0x00a4, 7054, 0x00a0, 7081, 0x009c, 7109, 0x0098, + 7136, 0x0094, 7164, 0x0090, 7192, 0x008c, 7219, 0x0088, 7247, 0x0084, + 7275, 0x0080, 7303, 0x007c, 7331, 0x0078, 7359, 0x0074, 7387, 0x0070, + 7415, 0x006c, 7443, 0x0068, 7471, 0x0064, 7499, 0x0060, 7527, 0x005c, + 7556, 0x0058, 7584, 0x0054, 7613, 0x0050, 7641, 0x004c, 7669, 0x0048, + 7698, 0x0044, 7727, 0x0040, 7755, 0x003c, 7784, 0x0038, 7813, 0x0034, + 7842, 0x0030, 7870, 0x002c, 7899, 0x0028, 7928, 0x0024, 7957, 0x0020, + 7986, 0x001c, 8016, 0x0018, 8045, 0x0014, 8074, 0x0010, 8103, 0x000c, + 8133, 0x0008, 8162, 0x0004, 8192, 0x0000 +}; + +static unsigned short lookup_volume_table( unsigned short value ) +{ + /* This code is an optimised version of: + * int i = 0; + * while( volume_table[i*2] < value ) + * i++; + * return volume_table[i*2+1]; + */ + unsigned short *ptr = log_table; + while( *ptr < value ) + ptr += 2; + return *(ptr+1); +} + +/* this function calculates a 8.8 fixed point logarithmic attenuation + * value from a linear volume value in the range 0 to 16384 */ +static unsigned short log_from_linear( unsigned short value ) +{ + if (value >= 16384) + return 0x0000; + if (value) { + unsigned short result = 0; + int v, c; + for( c = 0, v = 8192; c < 14; c++, v >>= 1 ) { + if( value >= v ) { + result += lookup_volume_table( (value - v) << c ); + return result; + } + result += 0x0605; /* 6.0205 (result of -20*log10(0.5)) */ + } + } + return 0xffff; +} + +/* + * Sample handling operations + */ + +static void sample_start(trident_t * trident, snd_trident_voice_t * voice, snd_seq_position_t position); +static void sample_stop(trident_t * trident, snd_trident_voice_t * voice, snd_seq_stop_mode_t mode); +static void sample_freq(trident_t * trident, snd_trident_voice_t * voice, snd_seq_frequency_t freq); +static void sample_volume(trident_t * trident, snd_trident_voice_t * voice, snd_seq_ev_volume_t * volume); +static void sample_loop(trident_t * trident, snd_trident_voice_t * voice, snd_seq_ev_loop_t * loop); +static void sample_pos(trident_t * trident, snd_trident_voice_t * voice, snd_seq_position_t position); +static void sample_private1(trident_t * trident, snd_trident_voice_t * voice, unsigned char *data); + +static snd_trident_sample_ops_t sample_ops = +{ + sample_start, + sample_stop, + sample_freq, + sample_volume, + sample_loop, + sample_pos, + sample_private1 +}; + +static void snd_trident_simple_init(snd_trident_voice_t * voice) +{ + //voice->handler_wave = interrupt_wave; + //voice->handler_volume = interrupt_volume; + //voice->handler_effect = interrupt_effect; + //voice->volume_change = NULL; + voice->sample_ops = &sample_ops; +} + +static void sample_start(trident_t * trident, snd_trident_voice_t * voice, snd_seq_position_t position) +{ + simple_instrument_t *simple; + snd_seq_kinstr_t *instr; + unsigned long flags; + unsigned int loop_start, loop_end, sample_start, sample_end, start_offset; + unsigned int value; + unsigned int shift = 0; + + instr = snd_seq_instr_find(trident->synth.ilist, &voice->instr, 0, 1); + if (instr == NULL) + return; + voice->instr = instr->instr; /* copy ID to speedup aliases */ + simple = KINSTR_DATA(instr); + + spin_lock_irqsave(&trident->reg_lock, flags); + + if (trident->device == TRIDENT_DEVICE_ID_SI7018) + voice->GVSel = 1; /* route to Wave volume */ + + voice->CTRL = 0; + voice->Alpha = 0; + voice->FMS = 0; + + loop_start = simple->loop_start >> 4; + loop_end = simple->loop_end >> 4; + sample_start = (simple->start + position) >> 4; + if( sample_start >= simple->size ) + sample_start = simple->start >> 4; + sample_end = simple->size; + start_offset = position >> 4; + + if (simple->format & SIMPLE_WAVE_16BIT) { + voice->CTRL |= 8; + shift++; + } + if (simple->format & SIMPLE_WAVE_STEREO) { + voice->CTRL |= 4; + shift++; + } + if (!(simple->format & SIMPLE_WAVE_UNSIGNED)) + voice->CTRL |= 2; + + voice->LBA = simple->address.memory; + + if (simple->format & SIMPLE_WAVE_LOOP) { + voice->CTRL |= 1; + voice->LBA += loop_start << shift; + if( start_offset >= loop_start ) { + voice->CSO = start_offset - loop_start; + voice->negCSO = 0; + } else { + voice->CSO = loop_start - start_offset; + voice->negCSO = 1; + } + voice->ESO = loop_end - loop_start - 1; + } else { + voice->LBA += start_offset << shift; + voice->CSO = sample_start; + voice->ESO = sample_end - 1; + voice->negCSO = 0; + } + + if (voice->flags & SNDRV_TRIDENT_VFLG_RUNNING) { + snd_trident_stop_voice(trident, voice->number); + voice->flags &= ~SNDRV_TRIDENT_VFLG_RUNNING; + } + + /* set CSO sign */ + value = inl(TRID_REG(trident, T4D_SIGN_CSO_A)); + if( voice->negCSO ) { + value |= 1 << (voice->number&31); + } else { + value &= ~(1 << (voice->number&31)); + } + outl(value,TRID_REG(trident, T4D_SIGN_CSO_A)); + + voice->Attribute = 0; + snd_trident_write_voice_regs(trident, voice); + snd_trident_start_voice(trident, voice->number); + voice->flags |= SNDRV_TRIDENT_VFLG_RUNNING; + spin_unlock_irqrestore(&trident->reg_lock, flags); + snd_seq_instr_free_use(trident->synth.ilist, instr); +} + +static void sample_stop(trident_t * trident, snd_trident_voice_t * voice, snd_seq_stop_mode_t mode) +{ + unsigned long flags; + + if (!(voice->flags & SNDRV_TRIDENT_VFLG_RUNNING)) + return; + + switch (mode) { + default: + spin_lock_irqsave(&trident->reg_lock, flags); + snd_trident_stop_voice(trident, voice->number); + voice->flags &= ~SNDRV_TRIDENT_VFLG_RUNNING; + spin_unlock_irqrestore(&trident->reg_lock, flags); + break; + case SAMPLE_STOP_LOOP: /* disable loop only */ + voice->CTRL &= ~1; + spin_lock_irqsave(&trident->reg_lock, flags); + outb((unsigned char) voice->number, TRID_REG(trident, T4D_LFO_GC_CIR)); + outw((((voice->CTRL << 12) | (voice->EC & 0x0fff)) & 0xffff), CH_GVSEL_PAN_VOL_CTRL_EC); + spin_unlock_irqrestore(&trident->reg_lock, flags); + break; + } +} + +static void sample_freq(trident_t * trident, snd_trident_voice_t * voice, snd_seq_frequency_t freq) +{ + unsigned long flags; + freq >>= 4; + + spin_lock_irqsave(&trident->reg_lock, flags); + if (freq == 44100) + voice->Delta = 0xeb3; + else if (freq == 8000) + voice->Delta = 0x2ab; + else if (freq == 48000) + voice->Delta = 0x1000; + else + voice->Delta = (((freq << 12) + freq) / 48000) & 0x0000ffff; + + outb((unsigned char) voice->number, TRID_REG(trident, T4D_LFO_GC_CIR)); + if (trident->device == TRIDENT_DEVICE_ID_NX) { + outb((unsigned char) voice->Delta, TRID_REG(trident, CH_NX_DELTA_CSO + 3)); + outb((unsigned char) (voice->Delta >> 8), TRID_REG(trident, CH_NX_DELTA_ESO + 3)); + } else { + outw((unsigned short) voice->Delta, TRID_REG(trident, CH_DX_ESO_DELTA)); + } + + spin_unlock_irqrestore(&trident->reg_lock, flags); +} + +static void sample_volume(trident_t * trident, snd_trident_voice_t * voice, snd_seq_ev_volume_t * volume) +{ + unsigned long flags; + unsigned short value; + + spin_lock_irqsave(&trident->reg_lock, flags); + voice->GVSel = 0; /* use global music volume */ + voice->FMC = 0x03; /* fixme: can we do something useful with FMC? */ + if (volume->volume >= 0) { + volume->volume &= 0x3fff; + /* linear volume -> logarithmic attenuation conversion + * uses EC register for greater resolution (6.6 bits) than Vol register (5.3 bits) + * Vol register used when additional attenuation is required */ + voice->RVol = 0; + voice->CVol = 0; + value = log_from_linear( volume->volume ); + voice->Vol = 0; + voice->EC = (value & 0x3fff) >> 2; + if (value > 0x3fff) { + voice->EC |= 0xfc0; + if (value < 0x5f00 ) + voice->Vol = ((value >> 8) - 0x3f) << 5; + else { + voice->Vol = 0x3ff; + voice->EC = 0xfff; + } + } + } + if (volume->lr >= 0) { + volume->lr &= 0x3fff; + /* approximate linear pan by attenuating channels */ + if (volume->lr >= 0x2000) { /* attenuate left (pan right) */ + value = 0x3fff - volume->lr; + for (voice->Pan = 0; voice->Pan < 63; voice->Pan++ ) + if (value >= pan_table[voice->Pan] ) + break; + } else { /* attenuate right (pan left) */ + for (voice->Pan = 0; voice->Pan < 63; voice->Pan++ ) + if ((unsigned int)volume->lr >= pan_table[voice->Pan] ) + break; + voice->Pan |= 0x40; + } + } + outb((unsigned char) voice->number, TRID_REG(trident, T4D_LFO_GC_CIR)); + outl((voice->GVSel << 31) | ((voice->Pan & 0x0000007f) << 24) | + ((voice->Vol & 0x000000ff) << 16) | ((voice->CTRL & 0x0000000f) << 12) | + (voice->EC & 0x00000fff), TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC)); + value = ((voice->FMC & 0x03) << 14) | ((voice->RVol & 0x7f) << 7) | (voice->CVol & 0x7f); + outw(value, TRID_REG(trident, CH_DX_FMC_RVOL_CVOL)); + spin_unlock_irqrestore(&trident->reg_lock, flags); +} + +static void sample_loop(trident_t * trident, snd_trident_voice_t * voice, snd_seq_ev_loop_t * loop) +{ + unsigned long flags; + simple_instrument_t *simple; + snd_seq_kinstr_t *instr; + unsigned int loop_start, loop_end; + + instr = snd_seq_instr_find(trident->synth.ilist, &voice->instr, 0, 1); + if (instr == NULL) + return; + voice->instr = instr->instr; /* copy ID to speedup aliases */ + simple = KINSTR_DATA(instr); + + loop_start = loop->start >> 4; + loop_end = loop->end >> 4; + + spin_lock_irqsave(&trident->reg_lock, flags); + + voice->LBA = simple->address.memory + loop_start; + voice->CSO = 0; + voice->ESO = loop_end - loop_start - 1; + + outb((unsigned char) voice->number, TRID_REG(trident, T4D_LFO_GC_CIR)); + outb((voice->LBA >> 16), TRID_REG(trident, CH_LBA + 2)); + outw((voice->LBA & 0xffff), TRID_REG(trident, CH_LBA)); + if (trident->device == TRIDENT_DEVICE_ID_NX) { + outb((voice->ESO >> 16), TRID_REG(trident, CH_NX_DELTA_ESO + 2)); + outw((voice->ESO & 0xffff), TRID_REG(trident, CH_NX_DELTA_ESO)); + outb((voice->CSO >> 16), TRID_REG(trident, CH_NX_DELTA_CSO + 2)); + outw((voice->CSO & 0xffff), TRID_REG(trident, CH_NX_DELTA_CSO)); + } else { + outw((voice->ESO & 0xffff), TRID_REG(trident, CH_DX_ESO_DELTA + 2)); + outw((voice->CSO & 0xffff), TRID_REG(trident, CH_DX_CSO_ALPHA_FMS + 2)); + } + + spin_unlock_irqrestore(&trident->reg_lock, flags); + snd_seq_instr_free_use(trident->synth.ilist, instr); +} + +static void sample_pos(trident_t * trident, snd_trident_voice_t * voice, snd_seq_position_t position) +{ + unsigned long flags; + simple_instrument_t *simple; + snd_seq_kinstr_t *instr; + unsigned int value; + + instr = snd_seq_instr_find(trident->synth.ilist, &voice->instr, 0, 1); + if (instr == NULL) + return; + voice->instr = instr->instr; /* copy ID to speedup aliases */ + simple = KINSTR_DATA(instr); + + spin_lock_irqsave(&trident->reg_lock, flags); + + if (simple->format & SIMPLE_WAVE_LOOP) { + if( position >= simple->loop_start ) { + voice->CSO = (position - simple->loop_start) >> 4; + voice->negCSO = 0; + } else { + voice->CSO = (simple->loop_start - position) >> 4; + voice->negCSO = 1; + } + } else { + voice->CSO = position >> 4; + voice->negCSO = 0; + } + + /* set CSO sign */ + value = inl(TRID_REG(trident, T4D_SIGN_CSO_A)); + if( voice->negCSO ) { + value |= 1 << (voice->number&31); + } else { + value &= ~(1 << (voice->number&31)); + } + outl(value,TRID_REG(trident, T4D_SIGN_CSO_A)); + + + outb((unsigned char) voice->number, TRID_REG(trident, T4D_LFO_GC_CIR)); + if (trident->device == TRIDENT_DEVICE_ID_NX) { + outw((voice->CSO & 0xffff), TRID_REG(trident, CH_NX_DELTA_CSO)); + outb((voice->CSO >> 16), TRID_REG(trident, CH_NX_DELTA_CSO + 2)); + } else { + outw((voice->CSO & 0xffff), TRID_REG(trident, CH_DX_CSO_ALPHA_FMS) + 2); + } + + spin_unlock_irqrestore(&trident->reg_lock, flags); + snd_seq_instr_free_use(trident->synth.ilist, instr); +} + +static void sample_private1(trident_t * trident, snd_trident_voice_t * voice, unsigned char *data) +{ +} + +/* + * Memory management / sample loading + */ + +static int snd_trident_simple_put_sample(void *private_data, simple_instrument_t * instr, + char __user *data, long len, int atomic) +{ + trident_t *trident = private_data; + int size = instr->size; + int shift = 0; + + if (instr->format & SIMPLE_WAVE_BACKWARD || + instr->format & SIMPLE_WAVE_BIDIR || + instr->format & SIMPLE_WAVE_ULAW) + return -EINVAL; /* not supported */ + + if (instr->format & SIMPLE_WAVE_16BIT) + shift++; + if (instr->format & SIMPLE_WAVE_STEREO) + shift++; + size <<= shift; + + if (trident->synth.current_size + size > trident->synth.max_size) + return -ENOMEM; + + if (!access_ok(VERIFY_READ, data, size)) + return -EFAULT; + + if (trident->tlb.entries) { + snd_util_memblk_t *memblk; + memblk = snd_trident_synth_alloc(trident, size); + if (memblk == NULL) + return -ENOMEM; + if (snd_trident_synth_copy_from_user(trident, memblk, 0, data, size) ) { + snd_trident_synth_free(trident, memblk); + return -EFAULT; + } + instr->address.ptr = (unsigned char*)memblk; + instr->address.memory = memblk->offset; + } else { + struct snd_dma_buffer dmab; + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci), + size, &dmab) < 0) + return -ENOMEM; + + if (copy_from_user(dmab.area, data, size)) { + snd_dma_free_pages(&dmab); + return -EFAULT; + } + instr->address.ptr = dmab.area; + instr->address.memory = dmab.addr; + } + + trident->synth.current_size += size; + return 0; +} + +static int snd_trident_simple_get_sample(void *private_data, simple_instrument_t * instr, + char __user *data, long len, int atomic) +{ + //trident_t *trident = private_data; + int size = instr->size; + int shift = 0; + + if (instr->format & SIMPLE_WAVE_16BIT) + shift++; + if (instr->format & SIMPLE_WAVE_STEREO) + shift++; + size <<= shift; + + if (!access_ok(VERIFY_WRITE, data, size)) + return -EFAULT; + + /* FIXME: not implemented yet */ + + return -EBUSY; +} + +static int snd_trident_simple_remove_sample(void *private_data, simple_instrument_t * instr, + int atomic) +{ + trident_t *trident = private_data; + int size = instr->size; + + if (instr->format & SIMPLE_WAVE_16BIT) + size <<= 1; + if (instr->format & SIMPLE_WAVE_STEREO) + size <<= 1; + + if (trident->tlb.entries) { + snd_util_memblk_t *memblk = (snd_util_memblk_t*)instr->address.ptr; + if (memblk) + snd_trident_synth_free(trident, memblk); + else + return -EFAULT; + } else { + struct snd_dma_buffer dmab; + dmab.dev.type = SNDRV_DMA_TYPE_DEV; + dmab.dev.dev = snd_dma_pci_data(trident->pci); + dmab.area = instr->address.ptr; + dmab.addr = instr->address.memory; + dmab.bytes = size; + snd_dma_free_pages(&dmab); + } + + trident->synth.current_size -= size; + if (trident->synth.current_size < 0) /* shouldn't need this check... */ + trident->synth.current_size = 0; + + return 0; +} + +static void select_instrument(trident_t * trident, snd_trident_voice_t * v) +{ + snd_seq_kinstr_t *instr; + instr = snd_seq_instr_find(trident->synth.ilist, &v->instr, 0, 1); + if (instr != NULL) { + if (instr->ops) { + if (!strcmp(instr->ops->instr_type, SNDRV_SEQ_INSTR_ID_SIMPLE)) + snd_trident_simple_init(v); + } + snd_seq_instr_free_use(trident->synth.ilist, instr); + } +} + +/* + + */ + +static void event_sample(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v) +{ + if (v->sample_ops && v->sample_ops->sample_stop) + v->sample_ops->sample_stop(p->trident, v, SAMPLE_STOP_IMMEDIATELY); + v->instr.std = ev->data.sample.param.sample.std; + if (v->instr.std & 0xff000000) { /* private instrument */ + v->instr.std &= 0x00ffffff; + v->instr.std |= (unsigned int)ev->source.client << 24; + } + v->instr.bank = ev->data.sample.param.sample.bank; + v->instr.prg = ev->data.sample.param.sample.prg; + select_instrument(p->trident, v); +} + +static void event_cluster(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v) +{ + if (v->sample_ops && v->sample_ops->sample_stop) + v->sample_ops->sample_stop(p->trident, v, SAMPLE_STOP_IMMEDIATELY); + v->instr.cluster = ev->data.sample.param.cluster.cluster; + select_instrument(p->trident, v); +} + +static void event_start(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v) +{ + if (v->sample_ops && v->sample_ops->sample_start) + v->sample_ops->sample_start(p->trident, v, ev->data.sample.param.position); +} + +static void event_stop(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v) +{ + if (v->sample_ops && v->sample_ops->sample_stop) + v->sample_ops->sample_stop(p->trident, v, ev->data.sample.param.stop_mode); +} + +static void event_freq(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v) +{ + if (v->sample_ops && v->sample_ops->sample_freq) + v->sample_ops->sample_freq(p->trident, v, ev->data.sample.param.frequency); +} + +static void event_volume(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v) +{ + if (v->sample_ops && v->sample_ops->sample_volume) + v->sample_ops->sample_volume(p->trident, v, &ev->data.sample.param.volume); +} + +static void event_loop(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v) +{ + if (v->sample_ops && v->sample_ops->sample_loop) + v->sample_ops->sample_loop(p->trident, v, &ev->data.sample.param.loop); +} + +static void event_position(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v) +{ + if (v->sample_ops && v->sample_ops->sample_pos) + v->sample_ops->sample_pos(p->trident, v, ev->data.sample.param.position); +} + +static void event_private1(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v) +{ + if (v->sample_ops && v->sample_ops->sample_private1) + v->sample_ops->sample_private1(p->trident, v, (unsigned char *) &ev->data.sample.param.raw8); +} + +typedef void (trident_sample_event_handler_t) (snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v); + +static trident_sample_event_handler_t *trident_sample_event_handlers[9] = +{ + event_sample, + event_cluster, + event_start, + event_stop, + event_freq, + event_volume, + event_loop, + event_position, + event_private1 +}; + +static void snd_trident_sample_event(snd_seq_event_t * ev, snd_trident_port_t * p) +{ + int idx, voice; + trident_t *trident = p->trident; + snd_trident_voice_t *v; + unsigned long flags; + + idx = ev->type - SNDRV_SEQ_EVENT_SAMPLE; + if (idx < 0 || idx > 8) + return; + for (voice = 0; voice < 64; voice++) { + v = &trident->synth.voices[voice]; + if (v->use && v->client == ev->source.client && + v->port == ev->source.port && + v->index == ev->data.sample.channel) { + spin_lock_irqsave(&trident->event_lock, flags); + trident_sample_event_handlers[idx] (ev, p, v); + spin_unlock_irqrestore(&trident->event_lock, flags); + return; + } + } +} + +/* + + */ + +static void snd_trident_synth_free_voices(trident_t * trident, int client, int port) +{ + int idx; + snd_trident_voice_t *voice; + + for (idx = 0; idx < 32; idx++) { + voice = &trident->synth.voices[idx]; + if (voice->use && voice->client == client && voice->port == port) + snd_trident_free_voice(trident, voice); + } +} + +static int snd_trident_synth_use(void *private_data, snd_seq_port_subscribe_t * info) +{ + snd_trident_port_t *port = (snd_trident_port_t *) private_data; + trident_t *trident = port->trident; + snd_trident_voice_t *voice; + unsigned int idx; + unsigned long flags; + + if (info->voices > 32) + return -EINVAL; + spin_lock_irqsave(&trident->reg_lock, flags); + for (idx = 0; idx < info->voices; idx++) { + voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_SYNTH, info->sender.client, info->sender.port); + if (voice == NULL) { + snd_trident_synth_free_voices(trident, info->sender.client, info->sender.port); + spin_unlock_irqrestore(&trident->reg_lock, flags); + return -EBUSY; + } + voice->index = idx; + voice->Vol = 0x3ff; + voice->EC = 0x0fff; + } +#if 0 + for (idx = 0; idx < info->midi_voices; idx++) { + port->midi_has_voices = 1; + voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_MIDI, info->sender.client, info->sender.port); + if (voice == NULL) { + snd_trident_synth_free_voices(trident, info->sender.client, info->sender.port); + spin_unlock_irqrestore(&trident->reg_lock, flags); + return -EBUSY; + } + voice->Vol = 0x3ff; + voice->EC = 0x0fff; + } +#endif + spin_unlock_irqrestore(&trident->reg_lock, flags); + return 0; +} + +static int snd_trident_synth_unuse(void *private_data, snd_seq_port_subscribe_t * info) +{ + snd_trident_port_t *port = (snd_trident_port_t *) private_data; + trident_t *trident = port->trident; + unsigned long flags; + + spin_lock_irqsave(&trident->reg_lock, flags); + snd_trident_synth_free_voices(trident, info->sender.client, info->sender.port); + spin_unlock_irqrestore(&trident->reg_lock, flags); + return 0; +} + +/* + + */ + +static void snd_trident_synth_free_private_instruments(snd_trident_port_t * p, int client) +{ + snd_seq_instr_header_t ifree; + + memset(&ifree, 0, sizeof(ifree)); + ifree.cmd = SNDRV_SEQ_INSTR_FREE_CMD_PRIVATE; + snd_seq_instr_list_free_cond(p->trident->synth.ilist, &ifree, client, 0); +} + +static int snd_trident_synth_event_input(snd_seq_event_t * ev, int direct, void *private_data, int atomic, int hop) +{ + snd_trident_port_t *p = (snd_trident_port_t *) private_data; + + if (p == NULL) + return -EINVAL; + if (ev->type >= SNDRV_SEQ_EVENT_SAMPLE && + ev->type <= SNDRV_SEQ_EVENT_SAMPLE_PRIVATE1) { + snd_trident_sample_event(ev, p); + return 0; + } + if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM && + ev->source.port == SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE) { + if (ev->type == SNDRV_SEQ_EVENT_CLIENT_EXIT) { + snd_trident_synth_free_private_instruments(p, ev->data.addr.client); + return 0; + } + } + if (direct) { + if (ev->type >= SNDRV_SEQ_EVENT_INSTR_BEGIN) { + snd_seq_instr_event(&p->trident->synth.simple_ops.kops, + p->trident->synth.ilist, ev, + p->trident->synth.seq_client, atomic, hop); + return 0; + } + } + return 0; +} + +static void snd_trident_synth_instr_notify(void *private_data, + snd_seq_kinstr_t * instr, + int what) +{ + int idx; + trident_t *trident = private_data; + snd_trident_voice_t *pvoice; + unsigned long flags; + + spin_lock_irqsave(&trident->event_lock, flags); + for (idx = 0; idx < 64; idx++) { + pvoice = &trident->synth.voices[idx]; + if (pvoice->use && !memcmp(&pvoice->instr, &instr->instr, sizeof(pvoice->instr))) { + if (pvoice->sample_ops && pvoice->sample_ops->sample_stop) { + pvoice->sample_ops->sample_stop(trident, pvoice, SAMPLE_STOP_IMMEDIATELY); + } else { + snd_trident_stop_voice(trident, pvoice->number); + pvoice->flags &= ~SNDRV_TRIDENT_VFLG_RUNNING; + } + } + } + spin_unlock_irqrestore(&trident->event_lock, flags); +} + +/* + + */ + +static void snd_trident_synth_free_port(void *private_data) +{ + snd_trident_port_t *p = (snd_trident_port_t *) private_data; + + if (p) + snd_midi_channel_free_set(p->chset); +} + +static int snd_trident_synth_create_port(trident_t * trident, int idx) +{ + snd_trident_port_t *p; + snd_seq_port_callback_t callbacks; + char name[32]; + char *str; + int result; + + p = &trident->synth.seq_ports[idx]; + p->chset = snd_midi_channel_alloc_set(16); + if (p->chset == NULL) + return -ENOMEM; + p->chset->private_data = p; + p->trident = trident; + p->client = trident->synth.seq_client; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.owner = THIS_MODULE; + callbacks.use = snd_trident_synth_use; + callbacks.unuse = snd_trident_synth_unuse; + callbacks.event_input = snd_trident_synth_event_input; + callbacks.private_free = snd_trident_synth_free_port; + callbacks.private_data = p; + + str = "???"; + switch (trident->device) { + case TRIDENT_DEVICE_ID_DX: str = "Trident 4DWave-DX"; break; + case TRIDENT_DEVICE_ID_NX: str = "Trident 4DWave-NX"; break; + case TRIDENT_DEVICE_ID_SI7018: str = "SiS 7018"; break; + } + sprintf(name, "%s port %i", str, idx); + p->chset->port = snd_seq_event_port_attach(trident->synth.seq_client, + &callbacks, + SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE, + SNDRV_SEQ_PORT_TYPE_DIRECT_SAMPLE | + SNDRV_SEQ_PORT_TYPE_SYNTH, + 16, 0, + name); + if (p->chset->port < 0) { + result = p->chset->port; + snd_trident_synth_free_port(p); + return result; + } + p->port = p->chset->port; + return 0; +} + +/* + + */ + +static int snd_trident_synth_new_device(snd_seq_device_t *dev) +{ + trident_t *trident; + int client, i; + snd_seq_client_callback_t callbacks; + snd_seq_client_info_t cinfo; + snd_seq_port_subscribe_t sub; + snd_simple_ops_t *simpleops; + char *str; + + trident = *(trident_t **)SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (trident == NULL) + return -EINVAL; + + trident->synth.seq_client = -1; + + /* allocate new client */ + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.private_data = trident; + callbacks.allow_output = callbacks.allow_input = 1; + client = trident->synth.seq_client = + snd_seq_create_kernel_client(trident->card, 1, &callbacks); + if (client < 0) + return client; + + /* change name of client */ + memset(&cinfo, 0, sizeof(cinfo)); + cinfo.client = client; + cinfo.type = KERNEL_CLIENT; + str = "???"; + switch (trident->device) { + case TRIDENT_DEVICE_ID_DX: str = "Trident 4DWave-DX"; break; + case TRIDENT_DEVICE_ID_NX: str = "Trident 4DWave-NX"; break; + case TRIDENT_DEVICE_ID_SI7018: str = "SiS 7018"; break; + } + sprintf(cinfo.name, str); + snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo); + + for (i = 0; i < 4; i++) + snd_trident_synth_create_port(trident, i); + + trident->synth.ilist = snd_seq_instr_list_new(); + if (trident->synth.ilist == NULL) { + snd_seq_delete_kernel_client(client); + trident->synth.seq_client = -1; + return -ENOMEM; + } + trident->synth.ilist->flags = SNDRV_SEQ_INSTR_FLG_DIRECT; + + simpleops = &trident->synth.simple_ops; + snd_seq_simple_init(simpleops, trident, NULL); + simpleops->put_sample = snd_trident_simple_put_sample; + simpleops->get_sample = snd_trident_simple_get_sample; + simpleops->remove_sample = snd_trident_simple_remove_sample; + simpleops->notify = snd_trident_synth_instr_notify; + + memset(&sub, 0, sizeof(sub)); + sub.sender.client = SNDRV_SEQ_CLIENT_SYSTEM; + sub.sender.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE; + sub.dest.client = client; + sub.dest.port = 0; + snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &sub); + + return 0; +} + +static int snd_trident_synth_delete_device(snd_seq_device_t *dev) +{ + trident_t *trident; + + trident = *(trident_t **)SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (trident == NULL) + return -EINVAL; + + if (trident->synth.seq_client >= 0) { + snd_seq_delete_kernel_client(trident->synth.seq_client); + trident->synth.seq_client = -1; + } + if (trident->synth.ilist) + snd_seq_instr_list_free(&trident->synth.ilist); + return 0; +} + +static int __init alsa_trident_synth_init(void) +{ + static snd_seq_dev_ops_t ops = + { + snd_trident_synth_new_device, + snd_trident_synth_delete_device + }; + + return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_TRIDENT, &ops, + sizeof(trident_t*)); +} + +static void __exit alsa_trident_synth_exit(void) +{ + snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_TRIDENT); +} + +module_init(alsa_trident_synth_init) +module_exit(alsa_trident_synth_exit) |