summaryrefslogtreecommitdiffstats
path: root/sound/pci/trident
diff options
context:
space:
mode:
Diffstat (limited to 'sound/pci/trident')
-rw-r--r--sound/pci/trident/Makefile10
-rw-r--r--sound/pci/trident/trident.c148
-rw-r--r--sound/pci/trident/trident.h427
-rw-r--r--sound/pci/trident/trident_main.c3885
-rw-r--r--sound/pci/trident/trident_memory.c278
5 files changed, 4748 insertions, 0 deletions
diff --git a/sound/pci/trident/Makefile b/sound/pci/trident/Makefile
new file mode 100644
index 000000000..e8975bc37
--- /dev/null
+++ b/sound/pci/trident/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-trident-objs := trident.o trident_main.o trident_memory.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_TRIDENT) += snd-trident.o
diff --git a/sound/pci/trident/trident.c b/sound/pci/trident/trident.c
new file mode 100644
index 000000000..9922ab407
--- /dev/null
+++ b/sound/pci/trident/trident.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * 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
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include "trident.h"
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, <audio@tridentmicro.com>");
+MODULE_DESCRIPTION("Trident 4D-WaveDX/NX & SiS SI7018");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool 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 const struct pci_device_id snd_trident_ids[] = {
+ {PCI_DEVICE(PCI_VENDOR_ID_TRIDENT, PCI_DEVICE_ID_TRIDENT_4DWAVE_DX),
+ PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+ {PCI_DEVICE(PCI_VENDOR_ID_TRIDENT, PCI_DEVICE_ID_TRIDENT_4DWAVE_NX),
+ 0, 0, 0},
+ {PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_7018), 0, 0, 0},
+ { 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_trident_ids);
+
+static int snd_trident_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+{
+ static int dev;
+ struct snd_card *card;
+ struct snd_trident *trident;
+ const char *str;
+ int err, pcm_dev = 0;
+
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+ if (!enable[dev]) {
+ dev++;
+ return -ENOENT;
+ }
+
+ err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ sizeof(*trident), &card);
+ if (err < 0)
+ return err;
+ trident = card->private_data;
+
+ err = snd_trident_create(card, pci,
+ pcm_channels[dev],
+ ((pci->vendor << 16) | pci->device) == TRIDENT_DEVICE_ID_SI7018 ? 1 : 2,
+ wavetable_size[dev]);
+ if (err < 0)
+ 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, str);
+ sprintf(card->longname, "%s PCI Audio at 0x%lx, irq %d",
+ card->shortname, trident->port, trident->irq);
+
+ err = snd_trident_pcm(trident, pcm_dev++);
+ if (err < 0)
+ return err;
+ switch (trident->device) {
+ case TRIDENT_DEVICE_ID_DX:
+ case TRIDENT_DEVICE_ID_NX:
+ err = snd_trident_foldback_pcm(trident, pcm_dev++);
+ if (err < 0)
+ return err;
+ break;
+ }
+ if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018) {
+ err = snd_trident_spdif_pcm(trident, pcm_dev++);
+ if (err < 0)
+ return err;
+ }
+ if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+ err = snd_mpu401_uart_new(card, 0, MPU401_HW_TRID4DWAVE,
+ trident->midi_port,
+ MPU401_INFO_INTEGRATED |
+ MPU401_INFO_IRQ_HOOK,
+ -1, &trident->rmidi);
+ if (err < 0)
+ return err;
+ }
+
+ snd_trident_create_gameport(trident);
+
+ err = snd_card_register(card);
+ if (err < 0)
+ return err;
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static struct pci_driver trident_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_trident_ids,
+ .probe = snd_trident_probe,
+#ifdef CONFIG_PM_SLEEP
+ .driver = {
+ .pm = &snd_trident_pm,
+ },
+#endif
+};
+
+module_pci_driver(trident_driver);
diff --git a/sound/pci/trident/trident.h b/sound/pci/trident/trident.h
new file mode 100644
index 000000000..9768a7fc2
--- /dev/null
+++ b/sound/pci/trident/trident.h
@@ -0,0 +1,427 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_TRIDENT_H
+#define __SOUND_TRIDENT_H
+
+/*
+ * audio@tridentmicro.com
+ * Fri Feb 19 15:55:28 MST 1999
+ * Definitions for Trident 4DWave DX/NX chips
+ */
+
+#include <sound/pcm.h>
+#include <sound/mpu401.h>
+#include <sound/ac97_codec.h>
+#include <sound/util_mem.h>
+
+#define TRIDENT_DEVICE_ID_DX ((PCI_VENDOR_ID_TRIDENT<<16)|PCI_DEVICE_ID_TRIDENT_4DWAVE_DX)
+#define TRIDENT_DEVICE_ID_NX ((PCI_VENDOR_ID_TRIDENT<<16)|PCI_DEVICE_ID_TRIDENT_4DWAVE_NX)
+#define TRIDENT_DEVICE_ID_SI7018 ((PCI_VENDOR_ID_SI<<16)|PCI_DEVICE_ID_SI_7018)
+
+#define SNDRV_TRIDENT_VOICE_TYPE_PCM 0
+#define SNDRV_TRIDENT_VOICE_TYPE_SYNTH 1
+#define SNDRV_TRIDENT_VOICE_TYPE_MIDI 2
+
+#define SNDRV_TRIDENT_VFLG_RUNNING (1<<0)
+
+/* TLB code constants */
+#define SNDRV_TRIDENT_PAGE_SIZE 4096
+#define SNDRV_TRIDENT_PAGE_SHIFT 12
+#define SNDRV_TRIDENT_PAGE_MASK ((1<<SNDRV_TRIDENT_PAGE_SHIFT)-1)
+#define SNDRV_TRIDENT_MAX_PAGES 4096
+
+/*
+ * Direct registers
+ */
+
+#define TRID_REG(trident, x) ((trident)->port + (x))
+
+#define ID_4DWAVE_DX 0x2000
+#define ID_4DWAVE_NX 0x2001
+
+/* Bank definitions */
+
+#define T4D_BANK_A 0
+#define T4D_BANK_B 1
+#define T4D_NUM_BANKS 2
+
+/* Register definitions */
+
+/* Global registers */
+
+enum global_control_bits {
+ CHANNEL_IDX = 0x0000003f,
+ OVERRUN_IE = 0x00000400, /* interrupt enable: capture overrun */
+ UNDERRUN_IE = 0x00000800, /* interrupt enable: playback underrun */
+ ENDLP_IE = 0x00001000, /* interrupt enable: end of buffer */
+ MIDLP_IE = 0x00002000, /* interrupt enable: middle buffer */
+ ETOG_IE = 0x00004000, /* interrupt enable: envelope toggling */
+ EDROP_IE = 0x00008000, /* interrupt enable: envelope drop */
+ BANK_B_EN = 0x00010000, /* SiS: enable bank B (64 channels) */
+ PCMIN_B_MIX = 0x00020000, /* SiS: PCM IN B mixing enable */
+ I2S_OUT_ASSIGN = 0x00040000, /* SiS: I2S Out contains surround PCM */
+ SPDIF_OUT_ASSIGN= 0x00080000, /* SiS: 0=S/PDIF L/R | 1=PCM Out FIFO */
+ MAIN_OUT_ASSIGN = 0x00100000, /* SiS: 0=PCM Out FIFO | 1=MMC Out buffer */
+};
+
+enum miscint_bits {
+ PB_UNDERRUN_IRQ = 0x00000001, REC_OVERRUN_IRQ = 0x00000002,
+ SB_IRQ = 0x00000004, MPU401_IRQ = 0x00000008,
+ OPL3_IRQ = 0x00000010, ADDRESS_IRQ = 0x00000020,
+ ENVELOPE_IRQ = 0x00000040, PB_UNDERRUN = 0x00000100,
+ REC_OVERRUN = 0x00000200, MIXER_UNDERFLOW = 0x00000400,
+ MIXER_OVERFLOW = 0x00000800, NX_SB_IRQ_DISABLE = 0x00001000,
+ ST_TARGET_REACHED = 0x00008000,
+ PB_24K_MODE = 0x00010000, ST_IRQ_EN = 0x00800000,
+ ACGPIO_IRQ = 0x01000000
+};
+
+/* T2 legacy dma control registers. */
+#define LEGACY_DMAR0 0x00 // ADR0
+#define LEGACY_DMAR4 0x04 // CNT0
+#define LEGACY_DMAR6 0x06 // CNT0 - High bits
+#define LEGACY_DMAR11 0x0b // MOD
+#define LEGACY_DMAR15 0x0f // MMR
+
+#define T4D_START_A 0x80
+#define T4D_STOP_A 0x84
+#define T4D_DLY_A 0x88
+#define T4D_SIGN_CSO_A 0x8c
+#define T4D_CSPF_A 0x90
+#define T4D_CSPF_B 0xbc
+#define T4D_CEBC_A 0x94
+#define T4D_AINT_A 0x98
+#define T4D_AINTEN_A 0x9c
+#define T4D_LFO_GC_CIR 0xa0
+#define T4D_MUSICVOL_WAVEVOL 0xa8
+#define T4D_SBDELTA_DELTA_R 0xac
+#define T4D_MISCINT 0xb0
+#define T4D_START_B 0xb4
+#define T4D_STOP_B 0xb8
+#define T4D_SBBL_SBCL 0xc0
+#define T4D_SBCTRL_SBE2R_SBDD 0xc4
+#define T4D_STIMER 0xc8
+#define T4D_AINT_B 0xd8
+#define T4D_AINTEN_B 0xdc
+#define T4D_RCI 0x70
+
+/* MPU-401 UART */
+#define T4D_MPU401_BASE 0x20
+#define T4D_MPUR0 0x20
+#define T4D_MPUR1 0x21
+#define T4D_MPUR2 0x22
+#define T4D_MPUR3 0x23
+
+/* S/PDIF Registers */
+#define NX_SPCTRL_SPCSO 0x24
+#define NX_SPLBA 0x28
+#define NX_SPESO 0x2c
+#define NX_SPCSTATUS 0x64
+
+/* Joystick */
+#define GAMEPORT_GCR 0x30
+#define GAMEPORT_MODE_ADC 0x80
+#define GAMEPORT_LEGACY 0x31
+#define GAMEPORT_AXES 0x34
+
+/* NX Specific Registers */
+#define NX_TLBC 0x6c
+
+/* Channel Registers */
+
+#define CH_START 0xe0
+
+#define CH_DX_CSO_ALPHA_FMS 0xe0
+#define CH_DX_ESO_DELTA 0xe8
+#define CH_DX_FMC_RVOL_CVOL 0xec
+
+#define CH_NX_DELTA_CSO 0xe0
+#define CH_NX_DELTA_ESO 0xe8
+#define CH_NX_ALPHA_FMS_FMC_RVOL_CVOL 0xec
+
+#define CH_LBA 0xe4
+#define CH_GVSEL_PAN_VOL_CTRL_EC 0xf0
+#define CH_EBUF1 0xf4
+#define CH_EBUF2 0xf8
+
+/* AC-97 Registers */
+
+#define DX_ACR0_AC97_W 0x40
+#define DX_ACR1_AC97_R 0x44
+#define DX_ACR2_AC97_COM_STAT 0x48
+
+#define NX_ACR0_AC97_COM_STAT 0x40
+#define NX_ACR1_AC97_W 0x44
+#define NX_ACR2_AC97_R_PRIMARY 0x48
+#define NX_ACR3_AC97_R_SECONDARY 0x4c
+
+#define SI_AC97_WRITE 0x40
+#define SI_AC97_READ 0x44
+#define SI_SERIAL_INTF_CTRL 0x48
+#define SI_AC97_GPIO 0x4c
+#define SI_ASR0 0x50
+#define SI_SPDIF_CS 0x70
+#define SI_GPIO 0x7c
+
+enum trident_nx_ac97_bits {
+ /* ACR1-3 */
+ NX_AC97_BUSY_WRITE = 0x0800,
+ NX_AC97_BUSY_READ = 0x0800,
+ NX_AC97_BUSY_DATA = 0x0400,
+ NX_AC97_WRITE_SECONDARY = 0x0100,
+ /* ACR0 */
+ NX_AC97_SECONDARY_READY = 0x0040,
+ NX_AC97_SECONDARY_RECORD = 0x0020,
+ NX_AC97_SURROUND_OUTPUT = 0x0010,
+ NX_AC97_PRIMARY_READY = 0x0008,
+ NX_AC97_PRIMARY_RECORD = 0x0004,
+ NX_AC97_PCM_OUTPUT = 0x0002,
+ NX_AC97_WARM_RESET = 0x0001
+};
+
+enum trident_dx_ac97_bits {
+ DX_AC97_BUSY_WRITE = 0x8000,
+ DX_AC97_BUSY_READ = 0x8000,
+ DX_AC97_READY = 0x0010,
+ DX_AC97_RECORD = 0x0008,
+ DX_AC97_PLAYBACK = 0x0002
+};
+
+enum sis7018_ac97_bits {
+ SI_AC97_BUSY_WRITE = 0x00008000,
+ SI_AC97_AUDIO_BUSY = 0x00004000,
+ SI_AC97_MODEM_BUSY = 0x00002000,
+ SI_AC97_BUSY_READ = 0x00008000,
+ SI_AC97_SECONDARY = 0x00000080,
+};
+
+enum serial_intf_ctrl_bits {
+ WARM_RESET = 0x00000001,
+ COLD_RESET = 0x00000002,
+ I2S_CLOCK = 0x00000004,
+ PCM_SEC_AC97 = 0x00000008,
+ AC97_DBL_RATE = 0x00000010,
+ SPDIF_EN = 0x00000020,
+ I2S_OUTPUT_EN = 0x00000040,
+ I2S_INPUT_EN = 0x00000080,
+ PCMIN = 0x00000100,
+ LINE1IN = 0x00000200,
+ MICIN = 0x00000400,
+ LINE2IN = 0x00000800,
+ HEAD_SET_IN = 0x00001000,
+ GPIOIN = 0x00002000,
+ /* 7018 spec says id = 01 but the demo board routed to 10
+ SECONDARY_ID= 0x00004000, */
+ SECONDARY_ID = 0x00004000,
+ PCMOUT = 0x00010000,
+ SURROUT = 0x00020000,
+ CENTEROUT = 0x00040000,
+ LFEOUT = 0x00080000,
+ LINE1OUT = 0x00100000,
+ LINE2OUT = 0x00200000,
+ GPIOOUT = 0x00400000,
+ SI_AC97_PRIMARY_READY = 0x01000000,
+ SI_AC97_SECONDARY_READY = 0x02000000,
+ SI_AC97_POWERDOWN = 0x04000000,
+};
+
+/* PCM defaults */
+
+#define T4D_DEFAULT_PCM_VOL 10 /* 0 - 255 */
+#define T4D_DEFAULT_PCM_PAN 0 /* 0 - 127 */
+#define T4D_DEFAULT_PCM_RVOL 127 /* 0 - 127 */
+#define T4D_DEFAULT_PCM_CVOL 127 /* 0 - 127 */
+
+struct snd_trident;
+struct snd_trident_voice;
+struct snd_trident_pcm_mixer;
+
+struct snd_trident_port {
+ struct snd_midi_channel_set * chset;
+ struct snd_trident * trident;
+ int mode; /* operation mode */
+ int client; /* sequencer client number */
+ int port; /* sequencer port number */
+ unsigned int midi_has_voices: 1;
+};
+
+struct snd_trident_memblk_arg {
+ short first_page, last_page;
+};
+
+struct snd_trident_tlb {
+ __le32 *entries; /* 16k-aligned TLB table */
+ dma_addr_t entries_dmaaddr; /* 16k-aligned PCI address to TLB table */
+ struct snd_dma_buffer *buffer;
+ struct snd_util_memhdr * memhdr; /* page allocation list */
+ struct snd_dma_buffer *silent_page;
+};
+
+struct snd_trident_voice {
+ unsigned int number;
+ unsigned int use: 1,
+ pcm: 1,
+ synth:1,
+ midi: 1;
+ unsigned int flags;
+ unsigned char client;
+ unsigned char port;
+ unsigned char index;
+
+ struct snd_trident_sample_ops *sample_ops;
+
+ /* channel parameters */
+ unsigned int CSO; /* 24 bits (16 on DX) */
+ unsigned int ESO; /* 24 bits (16 on DX) */
+ unsigned int LBA; /* 30 bits */
+ unsigned short EC; /* 12 bits */
+ unsigned short Alpha; /* 12 bits */
+ unsigned short Delta; /* 16 bits */
+ unsigned short Attribute; /* 16 bits - SiS 7018 */
+ unsigned short Vol; /* 12 bits (6.6) */
+ unsigned char Pan; /* 7 bits (1.4.2) */
+ unsigned char GVSel; /* 1 bit */
+ unsigned char RVol; /* 7 bits (5.2) */
+ unsigned char CVol; /* 7 bits (5.2) */
+ unsigned char FMC; /* 2 bits */
+ unsigned char CTRL; /* 4 bits */
+ unsigned char FMS; /* 4 bits */
+ unsigned char LFO; /* 8 bits */
+
+ unsigned int negCSO; /* nonzero - use negative CSO */
+
+ struct snd_util_memblk *memblk; /* memory block if TLB enabled */
+
+ /* PCM data */
+
+ struct snd_trident *trident;
+ struct snd_pcm_substream *substream;
+ struct snd_trident_voice *extra; /* extra PCM voice (acts as interrupt generator) */
+ unsigned int running: 1,
+ capture: 1,
+ spdif: 1,
+ foldback: 1,
+ isync: 1,
+ isync2: 1,
+ isync3: 1;
+ int foldback_chan; /* foldback subdevice number */
+ unsigned int stimer; /* global sample timer (to detect spurious interrupts) */
+ unsigned int spurious_threshold; /* spurious threshold */
+ unsigned int isync_mark;
+ unsigned int isync_max;
+ unsigned int isync_ESO;
+
+ /* --- */
+
+ void *private_data;
+ void (*private_free)(struct snd_trident_voice *voice);
+};
+
+struct snd_4dwave {
+ int seq_client;
+
+ struct snd_trident_port seq_ports[4];
+ struct snd_trident_voice voices[64];
+
+ int ChanSynthCount; /* number of allocated synth channels */
+ int max_size; /* maximum synth memory size in bytes */
+ int current_size; /* current allocated synth mem in bytes */
+};
+
+struct snd_trident_pcm_mixer {
+ struct snd_trident_voice *voice; /* active voice */
+ unsigned short vol; /* front volume */
+ unsigned char pan; /* pan control */
+ unsigned char rvol; /* rear volume */
+ unsigned char cvol; /* center volume */
+ unsigned char pad;
+};
+
+struct snd_trident {
+ int irq;
+
+ unsigned int device; /* device ID */
+
+ unsigned char bDMAStart;
+
+ unsigned long port;
+ unsigned long midi_port;
+
+ unsigned int spurious_irq_count;
+ unsigned int spurious_irq_max_delta;
+
+ struct snd_trident_tlb tlb; /* TLB entries for NX cards */
+
+ unsigned char spdif_ctrl;
+ unsigned char spdif_pcm_ctrl;
+ unsigned int spdif_bits;
+ unsigned int spdif_pcm_bits;
+ struct snd_kcontrol *spdif_pcm_ctl; /* S/PDIF settings */
+ unsigned int ac97_ctrl;
+
+ unsigned int ChanMap[2]; /* allocation map for hardware channels */
+
+ int ChanPCM; /* max number of PCM channels */
+ int ChanPCMcnt; /* actual number of PCM channels */
+
+ unsigned int ac97_detect: 1; /* 1 = AC97 in detection phase */
+ unsigned int in_suspend: 1; /* 1 during suspend/resume */
+
+ struct snd_4dwave synth; /* synth specific variables */
+
+ spinlock_t event_lock;
+ spinlock_t voice_alloc;
+
+ struct snd_dma_device dma_dev;
+
+ struct pci_dev *pci;
+ struct snd_card *card;
+ struct snd_pcm *pcm; /* ADC/DAC PCM */
+ struct snd_pcm *foldback; /* Foldback PCM */
+ struct snd_pcm *spdif; /* SPDIF PCM */
+ struct snd_rawmidi *rmidi;
+
+ struct snd_ac97_bus *ac97_bus;
+ struct snd_ac97 *ac97;
+ struct snd_ac97 *ac97_sec;
+
+ unsigned int musicvol_wavevol;
+ struct snd_trident_pcm_mixer pcm_mixer[32];
+ struct snd_kcontrol *ctl_vol; /* front volume */
+ struct snd_kcontrol *ctl_pan; /* pan */
+ struct snd_kcontrol *ctl_rvol; /* rear volume */
+ struct snd_kcontrol *ctl_cvol; /* center volume */
+
+ spinlock_t reg_lock;
+
+ struct gameport *gameport;
+};
+
+int snd_trident_create(struct snd_card *card,
+ struct pci_dev *pci,
+ int pcm_streams,
+ int pcm_spdif_device,
+ int max_wavetable_size);
+int snd_trident_create_gameport(struct snd_trident *trident);
+
+int snd_trident_pcm(struct snd_trident *trident, int device);
+int snd_trident_foldback_pcm(struct snd_trident *trident, int device);
+int snd_trident_spdif_pcm(struct snd_trident *trident, int device);
+int snd_trident_attach_synthesizer(struct snd_trident * trident);
+struct snd_trident_voice *snd_trident_alloc_voice(struct snd_trident * trident, int type,
+ int client, int port);
+void snd_trident_free_voice(struct snd_trident * trident, struct snd_trident_voice *voice);
+void snd_trident_start_voice(struct snd_trident * trident, unsigned int voice);
+void snd_trident_stop_voice(struct snd_trident * trident, unsigned int voice);
+void snd_trident_write_voice_regs(struct snd_trident * trident, struct snd_trident_voice *voice);
+extern const struct dev_pm_ops snd_trident_pm;
+
+/* TLB memory allocation */
+struct snd_util_memblk *snd_trident_alloc_pages(struct snd_trident *trident,
+ struct snd_pcm_substream *substream);
+int snd_trident_free_pages(struct snd_trident *trident, struct snd_util_memblk *blk);
+struct snd_util_memblk *snd_trident_synth_alloc(struct snd_trident *trident, unsigned int size);
+int snd_trident_synth_free(struct snd_trident *trident, struct snd_util_memblk *blk);
+int snd_trident_synth_copy_from_user(struct snd_trident *trident, struct snd_util_memblk *blk,
+ int offset, const char __user *data, int size);
+
+#endif /* __SOUND_TRIDENT_H */
diff --git a/sound/pci/trident/trident_main.c b/sound/pci/trident/trident_main.c
new file mode 100644
index 000000000..e98eea1e6
--- /dev/null
+++ b/sound/pci/trident/trident_main.c
@@ -0,0 +1,3885 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Maintained by Jaroslav Kysela <perex@perex.cz>
+ * Originated by audio@tridentmicro.com
+ * Fri Feb 19 15:55:28 MST 1999
+ * Routines for control of Trident 4DWave (DX and NX) chip
+ *
+ * BUGS:
+ *
+ * TODO:
+ * ---
+ *
+ * SiS7018 S/PDIF support by Thomas Winischhofer <thomas@winischhofer.net>
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/gameport.h>
+#include <linux/dma-mapping.h>
+#include <linux/export.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include "trident.h"
+#include <sound/asoundef.h>
+
+static int snd_trident_pcm_mixer_build(struct snd_trident *trident,
+ struct snd_trident_voice * voice,
+ struct snd_pcm_substream *substream);
+static int snd_trident_pcm_mixer_free(struct snd_trident *trident,
+ struct snd_trident_voice * voice,
+ struct snd_pcm_substream *substream);
+static irqreturn_t snd_trident_interrupt(int irq, void *dev_id);
+static int snd_trident_sis_reset(struct snd_trident *trident);
+
+static void snd_trident_clear_voices(struct snd_trident * trident,
+ unsigned short v_min, unsigned short v_max);
+static void snd_trident_free(struct snd_card *card);
+
+/*
+ * common I/O routines
+ */
+
+
+#if 0
+static void snd_trident_print_voice_regs(struct snd_trident *trident, int voice)
+{
+ unsigned int val, tmp;
+
+ dev_dbg(trident->card->dev, "Trident voice %i:\n", voice);
+ outb(voice, TRID_REG(trident, T4D_LFO_GC_CIR));
+ val = inl(TRID_REG(trident, CH_LBA));
+ dev_dbg(trident->card->dev, "LBA: 0x%x\n", val);
+ val = inl(TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC));
+ dev_dbg(trident->card->dev, "GVSel: %i\n", val >> 31);
+ dev_dbg(trident->card->dev, "Pan: 0x%x\n", (val >> 24) & 0x7f);
+ dev_dbg(trident->card->dev, "Vol: 0x%x\n", (val >> 16) & 0xff);
+ dev_dbg(trident->card->dev, "CTRL: 0x%x\n", (val >> 12) & 0x0f);
+ dev_dbg(trident->card->dev, "EC: 0x%x\n", val & 0x0fff);
+ if (trident->device != TRIDENT_DEVICE_ID_NX) {
+ val = inl(TRID_REG(trident, CH_DX_CSO_ALPHA_FMS));
+ dev_dbg(trident->card->dev, "CSO: 0x%x\n", val >> 16);
+ dev_dbg(trident->card->dev, "Alpha: 0x%x\n", (val >> 4) & 0x0fff);
+ dev_dbg(trident->card->dev, "FMS: 0x%x\n", val & 0x0f);
+ val = inl(TRID_REG(trident, CH_DX_ESO_DELTA));
+ dev_dbg(trident->card->dev, "ESO: 0x%x\n", val >> 16);
+ dev_dbg(trident->card->dev, "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;
+ dev_dbg(trident->card->dev, "CSO: 0x%x\n", val & 0x00ffffff);
+ val = inl(TRID_REG(trident, CH_NX_DELTA_ESO));
+ tmp |= (val >> 16) & 0xff00;
+ dev_dbg(trident->card->dev, "Delta: 0x%x\n", tmp);
+ dev_dbg(trident->card->dev, "ESO: 0x%x\n", val & 0x00ffffff);
+ val = inl(TRID_REG(trident, CH_NX_ALPHA_FMS_FMC_RVOL_CVOL));
+ dev_dbg(trident->card->dev, "Alpha: 0x%x\n", val >> 20);
+ dev_dbg(trident->card->dev, "FMS: 0x%x\n", (val >> 16) & 0x0f);
+ }
+ dev_dbg(trident->card->dev, "FMC: 0x%x\n", (val >> 14) & 3);
+ dev_dbg(trident->card->dev, "RVol: 0x%x\n", (val >> 7) & 0x7f);
+ dev_dbg(trident->card->dev, "CVol: 0x%x\n", val & 0x7f);
+}
+#endif
+
+/*---------------------------------------------------------------------------
+ unsigned short snd_trident_codec_read(struct snd_ac97 *ac97, unsigned short reg)
+
+ Description: This routine will do all of the reading from the external
+ CODEC (AC97).
+
+ Parameters: ac97 - ac97 codec structure
+ reg - CODEC register index, from AC97 Hal.
+
+ returns: 16 bit value read from the AC97.
+
+ ---------------------------------------------------------------------------*/
+static unsigned short snd_trident_codec_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+ unsigned int data = 0, treg;
+ unsigned short count = 0xffff;
+ unsigned long flags;
+ struct snd_trident *trident = ac97->private_data;
+
+ spin_lock_irqsave(&trident->reg_lock, flags);
+ if (trident->device == TRIDENT_DEVICE_ID_DX) {
+ data = (DX_AC97_BUSY_READ | (reg & 0x000000ff));
+ outl(data, TRID_REG(trident, DX_ACR1_AC97_R));
+ do {
+ data = inl(TRID_REG(trident, DX_ACR1_AC97_R));
+ if ((data & DX_AC97_BUSY_READ) == 0)
+ break;
+ } while (--count);
+ } else if (trident->device == TRIDENT_DEVICE_ID_NX) {
+ data = (NX_AC97_BUSY_READ | (reg & 0x000000ff));
+ treg = ac97->num == 0 ? NX_ACR2_AC97_R_PRIMARY : NX_ACR3_AC97_R_SECONDARY;
+ outl(data, TRID_REG(trident, treg));
+ do {
+ data = inl(TRID_REG(trident, treg));
+ if ((data & 0x00000C00) == 0)
+ break;
+ } while (--count);
+ } else if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+ data = SI_AC97_BUSY_READ | SI_AC97_AUDIO_BUSY | (reg & 0x000000ff);
+ if (ac97->num == 1)
+ data |= SI_AC97_SECONDARY;
+ outl(data, TRID_REG(trident, SI_AC97_READ));
+ do {
+ data = inl(TRID_REG(trident, SI_AC97_READ));
+ if ((data & (SI_AC97_BUSY_READ)) == 0)
+ break;
+ } while (--count);
+ }
+
+ if (count == 0 && !trident->ac97_detect) {
+ dev_err(trident->card->dev,
+ "ac97 codec read TIMEOUT [0x%x/0x%x]!!!\n",
+ reg, data);
+ data = 0;
+ }
+
+ spin_unlock_irqrestore(&trident->reg_lock, flags);
+ return ((unsigned short) (data >> 16));
+}
+
+/*---------------------------------------------------------------------------
+ void snd_trident_codec_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short wdata)
+
+ Description: This routine will do all of the writing to the external
+ CODEC (AC97).
+
+ Parameters: ac97 - ac97 codec structure
+ reg - CODEC register index, from AC97 Hal.
+ data - Lower 16 bits are the data to write to CODEC.
+
+ returns: TRUE if everything went ok, else FALSE.
+
+ ---------------------------------------------------------------------------*/
+static void snd_trident_codec_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short wdata)
+{
+ unsigned int address, data;
+ unsigned short count = 0xffff;
+ unsigned long flags;
+ struct snd_trident *trident = ac97->private_data;
+
+ data = ((unsigned long) wdata) << 16;
+
+ spin_lock_irqsave(&trident->reg_lock, flags);
+ if (trident->device == TRIDENT_DEVICE_ID_DX) {
+ address = DX_ACR0_AC97_W;
+
+ /* read AC-97 write register status */
+ do {
+ if ((inw(TRID_REG(trident, address)) & DX_AC97_BUSY_WRITE) == 0)
+ break;
+ } while (--count);
+
+ data |= (DX_AC97_BUSY_WRITE | (reg & 0x000000ff));
+ } else if (trident->device == TRIDENT_DEVICE_ID_NX) {
+ address = NX_ACR1_AC97_W;
+
+ /* read AC-97 write register status */
+ do {
+ if ((inw(TRID_REG(trident, address)) & NX_AC97_BUSY_WRITE) == 0)
+ break;
+ } while (--count);
+
+ data |= (NX_AC97_BUSY_WRITE | (ac97->num << 8) | (reg & 0x000000ff));
+ } else if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+ address = SI_AC97_WRITE;
+
+ /* read AC-97 write register status */
+ do {
+ if ((inw(TRID_REG(trident, address)) & (SI_AC97_BUSY_WRITE)) == 0)
+ break;
+ } while (--count);
+
+ data |= SI_AC97_BUSY_WRITE | SI_AC97_AUDIO_BUSY | (reg & 0x000000ff);
+ if (ac97->num == 1)
+ data |= SI_AC97_SECONDARY;
+ } else {
+ address = 0; /* keep GCC happy */
+ count = 0; /* return */
+ }
+
+ if (count == 0) {
+ spin_unlock_irqrestore(&trident->reg_lock, flags);
+ return;
+ }
+ outl(data, TRID_REG(trident, address));
+ spin_unlock_irqrestore(&trident->reg_lock, flags);
+}
+
+/*---------------------------------------------------------------------------
+ void snd_trident_enable_eso(struct snd_trident *trident)
+
+ Description: This routine will enable end of loop interrupts.
+ End of loop interrupts will occur when a running
+ channel reaches ESO.
+ Also enables middle of loop interrupts.
+
+ Parameters: trident - pointer to target device class for 4DWave.
+
+ ---------------------------------------------------------------------------*/
+
+static void snd_trident_enable_eso(struct snd_trident * trident)
+{
+ unsigned int val;
+
+ val = inl(TRID_REG(trident, T4D_LFO_GC_CIR));
+ val |= ENDLP_IE;
+ val |= MIDLP_IE;
+ if (trident->device == TRIDENT_DEVICE_ID_SI7018)
+ val |= BANK_B_EN;
+ outl(val, TRID_REG(trident, T4D_LFO_GC_CIR));
+}
+
+/*---------------------------------------------------------------------------
+ void snd_trident_disable_eso(struct snd_trident *trident)
+
+ Description: This routine will disable end of loop interrupts.
+ End of loop interrupts will occur when a running
+ channel reaches ESO.
+ Also disables middle of loop interrupts.
+
+ Parameters:
+ trident - pointer to target device class for 4DWave.
+
+ returns: TRUE if everything went ok, else FALSE.
+
+ ---------------------------------------------------------------------------*/
+
+static void snd_trident_disable_eso(struct snd_trident * trident)
+{
+ unsigned int tmp;
+
+ tmp = inl(TRID_REG(trident, T4D_LFO_GC_CIR));
+ tmp &= ~ENDLP_IE;
+ tmp &= ~MIDLP_IE;
+ outl(tmp, TRID_REG(trident, T4D_LFO_GC_CIR));
+}
+
+/*---------------------------------------------------------------------------
+ void snd_trident_start_voice(struct snd_trident * trident, unsigned int voice)
+
+ Description: Start a voice, any channel 0 thru 63.
+ This routine automatically handles the fact that there are
+ more than 32 channels available.
+
+ Parameters : voice - Voice number 0 thru n.
+ trident - pointer to target device class for 4DWave.
+
+ Return Value: None.
+
+ ---------------------------------------------------------------------------*/
+
+void snd_trident_start_voice(struct snd_trident * trident, unsigned int voice)
+{
+ unsigned int mask = 1 << (voice & 0x1f);
+ unsigned int reg = (voice & 0x20) ? T4D_START_B : T4D_START_A;
+
+ outl(mask, TRID_REG(trident, reg));
+}
+
+EXPORT_SYMBOL(snd_trident_start_voice);
+
+/*---------------------------------------------------------------------------
+ void snd_trident_stop_voice(struct snd_trident * trident, unsigned int voice)
+
+ Description: Stop a voice, any channel 0 thru 63.
+ This routine automatically handles the fact that there are
+ more than 32 channels available.
+
+ Parameters : voice - Voice number 0 thru n.
+ trident - pointer to target device class for 4DWave.
+
+ Return Value: None.
+
+ ---------------------------------------------------------------------------*/
+
+void snd_trident_stop_voice(struct snd_trident * trident, unsigned int voice)
+{
+ unsigned int mask = 1 << (voice & 0x1f);
+ unsigned int reg = (voice & 0x20) ? T4D_STOP_B : T4D_STOP_A;
+
+ outl(mask, TRID_REG(trident, reg));
+}
+
+EXPORT_SYMBOL(snd_trident_stop_voice);
+
+/*---------------------------------------------------------------------------
+ int snd_trident_allocate_pcm_channel(struct snd_trident *trident)
+
+ Description: Allocate hardware channel in Bank B (32-63).
+
+ Parameters : trident - pointer to target device class for 4DWave.
+
+ Return Value: hardware channel - 32-63 or -1 when no channel is available
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_allocate_pcm_channel(struct snd_trident * trident)
+{
+ int idx;
+
+ if (trident->ChanPCMcnt >= trident->ChanPCM)
+ return -1;
+ for (idx = 31; idx >= 0; idx--) {
+ if (!(trident->ChanMap[T4D_BANK_B] & (1 << idx))) {
+ trident->ChanMap[T4D_BANK_B] |= 1 << idx;
+ trident->ChanPCMcnt++;
+ return idx + 32;
+ }
+ }
+ return -1;
+}
+
+/*---------------------------------------------------------------------------
+ void snd_trident_free_pcm_channel(int channel)
+
+ Description: Free hardware channel in Bank B (32-63)
+
+ Parameters : trident - pointer to target device class for 4DWave.
+ channel - hardware channel number 0-63
+
+ Return Value: none
+
+ ---------------------------------------------------------------------------*/
+
+static void snd_trident_free_pcm_channel(struct snd_trident *trident, int channel)
+{
+ if (channel < 32 || channel > 63)
+ return;
+ channel &= 0x1f;
+ if (trident->ChanMap[T4D_BANK_B] & (1 << channel)) {
+ trident->ChanMap[T4D_BANK_B] &= ~(1 << channel);
+ trident->ChanPCMcnt--;
+ }
+}
+
+/*---------------------------------------------------------------------------
+ unsigned int snd_trident_allocate_synth_channel(void)
+
+ Description: Allocate hardware channel in Bank A (0-31).
+
+ Parameters : trident - pointer to target device class for 4DWave.
+
+ Return Value: hardware channel - 0-31 or -1 when no channel is available
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_allocate_synth_channel(struct snd_trident * trident)
+{
+ int idx;
+
+ for (idx = 31; idx >= 0; idx--) {
+ if (!(trident->ChanMap[T4D_BANK_A] & (1 << idx))) {
+ trident->ChanMap[T4D_BANK_A] |= 1 << idx;
+ trident->synth.ChanSynthCount++;
+ return idx;
+ }
+ }
+ return -1;
+}
+
+/*---------------------------------------------------------------------------
+ void snd_trident_free_synth_channel( int channel )
+
+ Description: Free hardware channel in Bank B (0-31).
+
+ Parameters : trident - pointer to target device class for 4DWave.
+ channel - hardware channel number 0-63
+
+ Return Value: none
+
+ ---------------------------------------------------------------------------*/
+
+static void snd_trident_free_synth_channel(struct snd_trident *trident, int channel)
+{
+ if (channel < 0 || channel > 31)
+ return;
+ channel &= 0x1f;
+ if (trident->ChanMap[T4D_BANK_A] & (1 << channel)) {
+ trident->ChanMap[T4D_BANK_A] &= ~(1 << channel);
+ trident->synth.ChanSynthCount--;
+ }
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_write_voice_regs
+
+ Description: This routine will complete and write the 5 hardware channel
+ registers to hardware.
+
+ Parameters: trident - pointer to target device class for 4DWave.
+ voice - synthesizer voice structure
+ Each register field.
+
+ ---------------------------------------------------------------------------*/
+
+void snd_trident_write_voice_regs(struct snd_trident * trident,
+ struct snd_trident_voice * voice)
+{
+ unsigned int FmcRvolCvol;
+ unsigned int regs[5];
+
+ regs[1] = voice->LBA;
+ regs[4] = (voice->GVSel << 31) |
+ ((voice->Pan & 0x0000007f) << 24) |
+ ((voice->CTRL & 0x0000000f) << 12);
+ FmcRvolCvol = ((voice->FMC & 3) << 14) |
+ ((voice->RVol & 0x7f) << 7) |
+ (voice->CVol & 0x7f);
+
+ switch (trident->device) {
+ case TRIDENT_DEVICE_ID_SI7018:
+ regs[4] |= voice->number > 31 ?
+ (voice->Vol & 0x000003ff) :
+ ((voice->Vol & 0x00003fc) << (16-2)) |
+ (voice->EC & 0x00000fff);
+ regs[0] = (voice->CSO << 16) | ((voice->Alpha & 0x00000fff) << 4) |
+ (voice->FMS & 0x0000000f);
+ regs[2] = (voice->ESO << 16) | (voice->Delta & 0x0ffff);
+ regs[3] = (voice->Attribute << 16) | FmcRvolCvol;
+ break;
+ case TRIDENT_DEVICE_ID_DX:
+ regs[4] |= ((voice->Vol & 0x000003fc) << (16-2)) |
+ (voice->EC & 0x00000fff);
+ regs[0] = (voice->CSO << 16) | ((voice->Alpha & 0x00000fff) << 4) |
+ (voice->FMS & 0x0000000f);
+ regs[2] = (voice->ESO << 16) | (voice->Delta & 0x0ffff);
+ regs[3] = FmcRvolCvol;
+ break;
+ case TRIDENT_DEVICE_ID_NX:
+ regs[4] |= ((voice->Vol & 0x000003fc) << (16-2)) |
+ (voice->EC & 0x00000fff);
+ regs[0] = (voice->Delta << 24) | (voice->CSO & 0x00ffffff);
+ regs[2] = ((voice->Delta << 16) & 0xff000000) |
+ (voice->ESO & 0x00ffffff);
+ regs[3] = (voice->Alpha << 20) |
+ ((voice->FMS & 0x0000000f) << 16) | FmcRvolCvol;
+ break;
+ default:
+ snd_BUG();
+ return;
+ }
+
+ outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+ outl(regs[0], TRID_REG(trident, CH_START + 0));
+ outl(regs[1], TRID_REG(trident, CH_START + 4));
+ outl(regs[2], TRID_REG(trident, CH_START + 8));
+ outl(regs[3], TRID_REG(trident, CH_START + 12));
+ outl(regs[4], TRID_REG(trident, CH_START + 16));
+
+#if 0
+ dev_dbg(trident->card->dev, "written %i channel:\n", voice->number);
+ dev_dbg(trident->card->dev, " regs[0] = 0x%x/0x%x\n",
+ regs[0], inl(TRID_REG(trident, CH_START + 0)));
+ dev_dbg(trident->card->dev, " regs[1] = 0x%x/0x%x\n",
+ regs[1], inl(TRID_REG(trident, CH_START + 4)));
+ dev_dbg(trident->card->dev, " regs[2] = 0x%x/0x%x\n",
+ regs[2], inl(TRID_REG(trident, CH_START + 8)));
+ dev_dbg(trident->card->dev, " regs[3] = 0x%x/0x%x\n",
+ regs[3], inl(TRID_REG(trident, CH_START + 12)));
+ dev_dbg(trident->card->dev, " regs[4] = 0x%x/0x%x\n",
+ regs[4], inl(TRID_REG(trident, CH_START + 16)));
+#endif
+}
+
+EXPORT_SYMBOL(snd_trident_write_voice_regs);
+
+/*---------------------------------------------------------------------------
+ snd_trident_write_cso_reg
+
+ Description: This routine will write the new CSO offset
+ register to hardware.
+
+ Parameters: trident - pointer to target device class for 4DWave.
+ voice - synthesizer voice structure
+ CSO - new CSO value
+
+ ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_cso_reg(struct snd_trident * trident,
+ struct snd_trident_voice * voice,
+ unsigned int CSO)
+{
+ voice->CSO = CSO;
+ outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+ if (trident->device != TRIDENT_DEVICE_ID_NX) {
+ outw(voice->CSO, TRID_REG(trident, CH_DX_CSO_ALPHA_FMS) + 2);
+ } else {
+ outl((voice->Delta << 24) |
+ (voice->CSO & 0x00ffffff), TRID_REG(trident, CH_NX_DELTA_CSO));
+ }
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_write_eso_reg
+
+ Description: This routine will write the new ESO offset
+ register to hardware.
+
+ Parameters: trident - pointer to target device class for 4DWave.
+ voice - synthesizer voice structure
+ ESO - new ESO value
+
+ ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_eso_reg(struct snd_trident * trident,
+ struct snd_trident_voice * voice,
+ unsigned int ESO)
+{
+ voice->ESO = ESO;
+ outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+ if (trident->device != TRIDENT_DEVICE_ID_NX) {
+ outw(voice->ESO, TRID_REG(trident, CH_DX_ESO_DELTA) + 2);
+ } else {
+ outl(((voice->Delta << 16) & 0xff000000) | (voice->ESO & 0x00ffffff),
+ TRID_REG(trident, CH_NX_DELTA_ESO));
+ }
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_write_vol_reg
+
+ Description: This routine will write the new voice volume
+ register to hardware.
+
+ Parameters: trident - pointer to target device class for 4DWave.
+ voice - synthesizer voice structure
+ Vol - new voice volume
+
+ ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_vol_reg(struct snd_trident * trident,
+ struct snd_trident_voice * voice,
+ unsigned int Vol)
+{
+ voice->Vol = Vol;
+ outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+ switch (trident->device) {
+ case TRIDENT_DEVICE_ID_DX:
+ case TRIDENT_DEVICE_ID_NX:
+ outb(voice->Vol >> 2, TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC + 2));
+ break;
+ case TRIDENT_DEVICE_ID_SI7018:
+ /* dev_dbg(trident->card->dev, "voice->Vol = 0x%x\n", voice->Vol); */
+ outw((voice->CTRL << 12) | voice->Vol,
+ TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC));
+ break;
+ }
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_write_pan_reg
+
+ Description: This routine will write the new voice pan
+ register to hardware.
+
+ Parameters: trident - pointer to target device class for 4DWave.
+ voice - synthesizer voice structure
+ Pan - new pan value
+
+ ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_pan_reg(struct snd_trident * trident,
+ struct snd_trident_voice * voice,
+ unsigned int Pan)
+{
+ voice->Pan = Pan;
+ outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+ outb(((voice->GVSel & 0x01) << 7) | (voice->Pan & 0x7f),
+ TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC + 3));
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_write_rvol_reg
+
+ Description: This routine will write the new reverb volume
+ register to hardware.
+
+ Parameters: trident - pointer to target device class for 4DWave.
+ voice - synthesizer voice structure
+ RVol - new reverb volume
+
+ ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_rvol_reg(struct snd_trident * trident,
+ struct snd_trident_voice * voice,
+ unsigned int RVol)
+{
+ voice->RVol = RVol;
+ outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+ outw(((voice->FMC & 0x0003) << 14) | ((voice->RVol & 0x007f) << 7) |
+ (voice->CVol & 0x007f),
+ TRID_REG(trident, trident->device == TRIDENT_DEVICE_ID_NX ?
+ CH_NX_ALPHA_FMS_FMC_RVOL_CVOL : CH_DX_FMC_RVOL_CVOL));
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_write_cvol_reg
+
+ Description: This routine will write the new chorus volume
+ register to hardware.
+
+ Parameters: trident - pointer to target device class for 4DWave.
+ voice - synthesizer voice structure
+ CVol - new chorus volume
+
+ ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_cvol_reg(struct snd_trident * trident,
+ struct snd_trident_voice * voice,
+ unsigned int CVol)
+{
+ voice->CVol = CVol;
+ outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+ outw(((voice->FMC & 0x0003) << 14) | ((voice->RVol & 0x007f) << 7) |
+ (voice->CVol & 0x007f),
+ TRID_REG(trident, trident->device == TRIDENT_DEVICE_ID_NX ?
+ CH_NX_ALPHA_FMS_FMC_RVOL_CVOL : CH_DX_FMC_RVOL_CVOL));
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_convert_rate
+
+ Description: This routine converts rate in HZ to hardware delta value.
+
+ Parameters: trident - pointer to target device class for 4DWave.
+ rate - Real or Virtual channel number.
+
+ Returns: Delta value.
+
+ ---------------------------------------------------------------------------*/
+static unsigned int snd_trident_convert_rate(unsigned int rate)
+{
+ unsigned int delta;
+
+ // We special case 44100 and 8000 since rounding with the equation
+ // does not give us an accurate enough value. For 11025 and 22050
+ // the equation gives us the best answer. All other frequencies will
+ // also use the equation. JDW
+ if (rate == 44100)
+ delta = 0xeb3;
+ else if (rate == 8000)
+ delta = 0x2ab;
+ else if (rate == 48000)
+ delta = 0x1000;
+ else
+ delta = DIV_ROUND_CLOSEST(rate << 12, 48000) & 0x0000ffff;
+ return delta;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_convert_adc_rate
+
+ Description: This routine converts rate in HZ to hardware delta value.
+
+ Parameters: trident - pointer to target device class for 4DWave.
+ rate - Real or Virtual channel number.
+
+ Returns: Delta value.
+
+ ---------------------------------------------------------------------------*/
+static unsigned int snd_trident_convert_adc_rate(unsigned int rate)
+{
+ unsigned int delta;
+
+ // We special case 44100 and 8000 since rounding with the equation
+ // does not give us an accurate enough value. For 11025 and 22050
+ // the equation gives us the best answer. All other frequencies will
+ // also use the equation. JDW
+ if (rate == 44100)
+ delta = 0x116a;
+ else if (rate == 8000)
+ delta = 0x6000;
+ else if (rate == 48000)
+ delta = 0x1000;
+ else
+ delta = ((48000 << 12) / rate) & 0x0000ffff;
+ return delta;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_spurious_threshold
+
+ Description: This routine converts rate in HZ to spurious threshold.
+
+ Parameters: trident - pointer to target device class for 4DWave.
+ rate - Real or Virtual channel number.
+
+ Returns: Delta value.
+
+ ---------------------------------------------------------------------------*/
+static unsigned int snd_trident_spurious_threshold(unsigned int rate,
+ unsigned int period_size)
+{
+ unsigned int res = (rate * period_size) / 48000;
+ if (res < 64)
+ res = res / 2;
+ else
+ res -= 32;
+ return res;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_control_mode
+
+ Description: This routine returns a control mode for a PCM channel.
+
+ Parameters: trident - pointer to target device class for 4DWave.
+ substream - PCM substream
+
+ Returns: Control value.
+
+ ---------------------------------------------------------------------------*/
+static unsigned int snd_trident_control_mode(struct snd_pcm_substream *substream)
+{
+ unsigned int CTRL;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /* set ctrl mode
+ CTRL default: 8-bit (unsigned) mono, loop mode enabled
+ */
+ CTRL = 0x00000001;
+ if (snd_pcm_format_width(runtime->format) == 16)
+ CTRL |= 0x00000008; // 16-bit data
+ if (snd_pcm_format_signed(runtime->format))
+ CTRL |= 0x00000002; // signed data
+ if (runtime->channels > 1)
+ CTRL |= 0x00000004; // stereo data
+ return CTRL;
+}
+
+/*
+ * PCM part
+ */
+
+/*---------------------------------------------------------------------------
+ snd_trident_allocate_pcm_mem
+
+ Description: Allocate PCM ring buffer for given substream
+
+ Parameters: substream - PCM substream class
+ hw_params - hardware parameters
+
+ Returns: Error status
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_allocate_pcm_mem(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_trident_voice *voice = runtime->private_data;
+
+ if (trident->tlb.entries) {
+ if (runtime->buffer_changed) {
+ if (voice->memblk)
+ snd_trident_free_pages(trident, voice->memblk);
+ voice->memblk = snd_trident_alloc_pages(trident, substream);
+ if (voice->memblk == NULL)
+ return -ENOMEM;
+ }
+ }
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_allocate_evoice
+
+ Description: Allocate extra voice as interrupt generator
+
+ Parameters: substream - PCM substream class
+ hw_params - hardware parameters
+
+ Returns: Error status
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_allocate_evoice(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_trident_voice *voice = runtime->private_data;
+ struct snd_trident_voice *evoice = voice->extra;
+
+ /* voice management */
+
+ if (params_buffer_size(hw_params) / 2 != params_period_size(hw_params)) {
+ if (evoice == NULL) {
+ evoice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
+ if (evoice == NULL)
+ return -ENOMEM;
+ voice->extra = evoice;
+ evoice->substream = substream;
+ }
+ } else {
+ if (evoice != NULL) {
+ snd_trident_free_voice(trident, evoice);
+ voice->extra = evoice = NULL;
+ }
+ }
+
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_hw_params
+
+ Description: Set the hardware parameters for the playback device.
+
+ Parameters: substream - PCM substream class
+ hw_params - hardware parameters
+
+ Returns: Error status
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ int err;
+
+ err = snd_trident_allocate_pcm_mem(substream, hw_params);
+ if (err >= 0)
+ err = snd_trident_allocate_evoice(substream, hw_params);
+ return err;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_playback_hw_free
+
+ Description: Release the hardware resources for the playback device.
+
+ Parameters: substream - PCM substream class
+
+ Returns: Error status
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_trident_voice *voice = runtime->private_data;
+ struct snd_trident_voice *evoice = voice ? voice->extra : NULL;
+
+ if (trident->tlb.entries) {
+ if (voice && voice->memblk) {
+ snd_trident_free_pages(trident, voice->memblk);
+ voice->memblk = NULL;
+ }
+ }
+ if (evoice != NULL) {
+ snd_trident_free_voice(trident, evoice);
+ voice->extra = NULL;
+ }
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_playback_prepare
+
+ Description: Prepare playback device for playback.
+
+ Parameters: substream - PCM substream class
+
+ Returns: Error status
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_trident_voice *voice = runtime->private_data;
+ struct snd_trident_voice *evoice = voice->extra;
+ struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[substream->number];
+
+ spin_lock_irq(&trident->reg_lock);
+
+ /* set delta (rate) value */
+ voice->Delta = snd_trident_convert_rate(runtime->rate);
+ voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
+
+ /* set Loop Begin Address */
+ if (voice->memblk)
+ voice->LBA = voice->memblk->offset;
+ else
+ voice->LBA = runtime->dma_addr;
+
+ voice->CSO = 0;
+ voice->ESO = runtime->buffer_size - 1; /* in samples */
+ voice->CTRL = snd_trident_control_mode(substream);
+ voice->FMC = 3;
+ voice->GVSel = 1;
+ voice->EC = 0;
+ voice->Alpha = 0;
+ voice->FMS = 0;
+ voice->Vol = mix->vol;
+ voice->RVol = mix->rvol;
+ voice->CVol = mix->cvol;
+ voice->Pan = mix->pan;
+ voice->Attribute = 0;
+#if 0
+ voice->Attribute = (1<<(30-16))|(2<<(26-16))|
+ (0<<(24-16))|(0x1f<<(19-16));
+#else
+ voice->Attribute = 0;
+#endif
+
+ snd_trident_write_voice_regs(trident, voice);
+
+ if (evoice != NULL) {
+ evoice->Delta = voice->Delta;
+ evoice->spurious_threshold = voice->spurious_threshold;
+ evoice->LBA = voice->LBA;
+ evoice->CSO = 0;
+ evoice->ESO = (runtime->period_size * 2) + 4 - 1; /* in samples */
+ evoice->CTRL = voice->CTRL;
+ evoice->FMC = 3;
+ evoice->GVSel = trident->device == TRIDENT_DEVICE_ID_SI7018 ? 0 : 1;
+ evoice->EC = 0;
+ evoice->Alpha = 0;
+ evoice->FMS = 0;
+ evoice->Vol = 0x3ff; /* mute */
+ evoice->RVol = evoice->CVol = 0x7f; /* mute */
+ evoice->Pan = 0x7f; /* mute */
+#if 0
+ evoice->Attribute = (1<<(30-16))|(2<<(26-16))|
+ (0<<(24-16))|(0x1f<<(19-16));
+#else
+ evoice->Attribute = 0;
+#endif
+ snd_trident_write_voice_regs(trident, evoice);
+ evoice->isync2 = 1;
+ evoice->isync_mark = runtime->period_size;
+ evoice->ESO = (runtime->period_size * 2) - 1;
+ }
+
+ spin_unlock_irq(&trident->reg_lock);
+
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_capture_hw_params
+
+ Description: Set the hardware parameters for the capture device.
+
+ Parameters: substream - PCM substream class
+ hw_params - hardware parameters
+
+ Returns: Error status
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_capture_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ return snd_trident_allocate_pcm_mem(substream, hw_params);
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_capture_prepare
+
+ Description: Prepare capture device for playback.
+
+ Parameters: substream - PCM substream class
+
+ Returns: Error status
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_trident_voice *voice = runtime->private_data;
+ unsigned int val, ESO_bytes;
+
+ spin_lock_irq(&trident->reg_lock);
+
+ // Initialize 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 = DIV_ROUND_CLOSEST(48000U << 12, runtime->rate);
+ outw(val, TRID_REG(trident, T4D_SBDELTA_DELTA_R));
+
+ // Set channel interrupt blk length
+ if (snd_pcm_format_width(runtime->format) == 16) {
+ val = (unsigned short) ((ESO_bytes >> 1) - 1);
+ } else {
+ val = (unsigned short) (ESO_bytes - 1);
+ }
+
+ outl((val << 16) | val, TRID_REG(trident, T4D_SBBL_SBCL));
+
+ // Right now, set format and start to run captureing,
+ // continuous run loop enable.
+ trident->bDMAStart = 0x19; // 0001 1001b
+
+ if (snd_pcm_format_width(runtime->format) == 16)
+ trident->bDMAStart |= 0x80;
+ if (snd_pcm_format_signed(runtime->format))
+ trident->bDMAStart |= 0x20;
+ if (runtime->channels > 1)
+ trident->bDMAStart |= 0x40;
+
+ // Prepare capture intr channel
+
+ voice->Delta = snd_trident_convert_rate(runtime->rate);
+ voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
+ voice->isync = 1;
+ voice->isync_mark = runtime->period_size;
+ voice->isync_max = runtime->buffer_size;
+
+ // Set voice parameters
+ voice->CSO = 0;
+ voice->ESO = voice->isync_ESO = (runtime->period_size * 2) + 6 - 1;
+ voice->CTRL = snd_trident_control_mode(substream);
+ voice->FMC = 3;
+ voice->RVol = 0x7f;
+ voice->CVol = 0x7f;
+ voice->GVSel = 1;
+ voice->Pan = 0x7f; /* mute */
+ voice->Vol = 0x3ff; /* mute */
+ voice->EC = 0;
+ voice->Alpha = 0;
+ voice->FMS = 0;
+ voice->Attribute = 0;
+
+ snd_trident_write_voice_regs(trident, voice);
+
+ spin_unlock_irq(&trident->reg_lock);
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_si7018_capture_hw_params
+
+ Description: Set the hardware parameters for the capture device.
+
+ Parameters: substream - PCM substream class
+ hw_params - hardware parameters
+
+ Returns: Error status
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_si7018_capture_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ return snd_trident_allocate_evoice(substream, hw_params);
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_si7018_capture_hw_free
+
+ Description: Release the hardware resources for the capture device.
+
+ Parameters: substream - PCM substream class
+
+ Returns: Error status
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_si7018_capture_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_trident_voice *voice = runtime->private_data;
+ struct snd_trident_voice *evoice = voice ? voice->extra : NULL;
+
+ if (evoice != NULL) {
+ snd_trident_free_voice(trident, evoice);
+ voice->extra = NULL;
+ }
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_si7018_capture_prepare
+
+ Description: Prepare capture device for playback.
+
+ Parameters: substream - PCM substream class
+
+ Returns: Error status
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_si7018_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_trident_voice *voice = runtime->private_data;
+ struct snd_trident_voice *evoice = voice->extra;
+
+ spin_lock_irq(&trident->reg_lock);
+
+ voice->LBA = runtime->dma_addr;
+ voice->Delta = snd_trident_convert_adc_rate(runtime->rate);
+ voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
+
+ // Set voice parameters
+ voice->CSO = 0;
+ voice->ESO = runtime->buffer_size - 1; /* in samples */
+ voice->CTRL = snd_trident_control_mode(substream);
+ voice->FMC = 0;
+ voice->RVol = 0;
+ voice->CVol = 0;
+ voice->GVSel = 1;
+ voice->Pan = T4D_DEFAULT_PCM_PAN;
+ voice->Vol = 0;
+ voice->EC = 0;
+ voice->Alpha = 0;
+ voice->FMS = 0;
+
+ voice->Attribute = (2 << (30-16)) |
+ (2 << (26-16)) |
+ (2 << (24-16)) |
+ (1 << (23-16));
+
+ snd_trident_write_voice_regs(trident, voice);
+
+ if (evoice != NULL) {
+ evoice->Delta = snd_trident_convert_rate(runtime->rate);
+ evoice->spurious_threshold = voice->spurious_threshold;
+ evoice->LBA = voice->LBA;
+ evoice->CSO = 0;
+ evoice->ESO = (runtime->period_size * 2) + 20 - 1; /* in samples, 20 means correction */
+ evoice->CTRL = voice->CTRL;
+ evoice->FMC = 3;
+ evoice->GVSel = 0;
+ evoice->EC = 0;
+ evoice->Alpha = 0;
+ evoice->FMS = 0;
+ evoice->Vol = 0x3ff; /* mute */
+ evoice->RVol = evoice->CVol = 0x7f; /* mute */
+ evoice->Pan = 0x7f; /* mute */
+ evoice->Attribute = 0;
+ snd_trident_write_voice_regs(trident, evoice);
+ evoice->isync2 = 1;
+ evoice->isync_mark = runtime->period_size;
+ evoice->ESO = (runtime->period_size * 2) - 1;
+ }
+
+ spin_unlock_irq(&trident->reg_lock);
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_foldback_prepare
+
+ Description: Prepare foldback capture device for playback.
+
+ Parameters: substream - PCM substream class
+
+ Returns: Error status
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_foldback_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_trident_voice *voice = runtime->private_data;
+ struct snd_trident_voice *evoice = voice->extra;
+
+ spin_lock_irq(&trident->reg_lock);
+
+ /* Set channel buffer Address */
+ if (voice->memblk)
+ voice->LBA = voice->memblk->offset;
+ else
+ voice->LBA = runtime->dma_addr;
+
+ /* set target ESO for channel */
+ voice->ESO = runtime->buffer_size - 1; /* in samples */
+
+ /* set sample rate */
+ voice->Delta = 0x1000;
+ voice->spurious_threshold = snd_trident_spurious_threshold(48000, runtime->period_size);
+
+ voice->CSO = 0;
+ voice->CTRL = snd_trident_control_mode(substream);
+ voice->FMC = 3;
+ voice->RVol = 0x7f;
+ voice->CVol = 0x7f;
+ voice->GVSel = 1;
+ voice->Pan = 0x7f; /* mute */
+ voice->Vol = 0x3ff; /* mute */
+ voice->EC = 0;
+ voice->Alpha = 0;
+ voice->FMS = 0;
+ voice->Attribute = 0;
+
+ /* set up capture channel */
+ outb(((voice->number & 0x3f) | 0x80), TRID_REG(trident, T4D_RCI + voice->foldback_chan));
+
+ snd_trident_write_voice_regs(trident, voice);
+
+ if (evoice != NULL) {
+ evoice->Delta = voice->Delta;
+ evoice->spurious_threshold = voice->spurious_threshold;
+ evoice->LBA = voice->LBA;
+ evoice->CSO = 0;
+ evoice->ESO = (runtime->period_size * 2) + 4 - 1; /* in samples */
+ evoice->CTRL = voice->CTRL;
+ evoice->FMC = 3;
+ evoice->GVSel = trident->device == TRIDENT_DEVICE_ID_SI7018 ? 0 : 1;
+ evoice->EC = 0;
+ evoice->Alpha = 0;
+ evoice->FMS = 0;
+ evoice->Vol = 0x3ff; /* mute */
+ evoice->RVol = evoice->CVol = 0x7f; /* mute */
+ evoice->Pan = 0x7f; /* mute */
+ evoice->Attribute = 0;
+ snd_trident_write_voice_regs(trident, evoice);
+ evoice->isync2 = 1;
+ evoice->isync_mark = runtime->period_size;
+ evoice->ESO = (runtime->period_size * 2) - 1;
+ }
+
+ spin_unlock_irq(&trident->reg_lock);
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_spdif_hw_params
+
+ Description: Set the hardware parameters for the spdif device.
+
+ Parameters: substream - PCM substream class
+ hw_params - hardware parameters
+
+ Returns: Error status
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ unsigned int old_bits = 0, change = 0;
+ int err;
+
+ err = snd_trident_allocate_pcm_mem(substream, hw_params);
+ if (err < 0)
+ return err;
+
+ if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+ err = snd_trident_allocate_evoice(substream, hw_params);
+ if (err < 0)
+ return err;
+ }
+
+ /* prepare SPDIF channel */
+ spin_lock_irq(&trident->reg_lock);
+ old_bits = trident->spdif_pcm_bits;
+ if (old_bits & IEC958_AES0_PROFESSIONAL)
+ trident->spdif_pcm_bits &= ~IEC958_AES0_PRO_FS;
+ else
+ trident->spdif_pcm_bits &= ~(IEC958_AES3_CON_FS << 24);
+ if (params_rate(hw_params) >= 48000) {
+ trident->spdif_pcm_ctrl = 0x3c; // 48000 Hz
+ trident->spdif_pcm_bits |=
+ trident->spdif_bits & IEC958_AES0_PROFESSIONAL ?
+ IEC958_AES0_PRO_FS_48000 :
+ (IEC958_AES3_CON_FS_48000 << 24);
+ }
+ else if (params_rate(hw_params) >= 44100) {
+ trident->spdif_pcm_ctrl = 0x3e; // 44100 Hz
+ trident->spdif_pcm_bits |=
+ trident->spdif_bits & IEC958_AES0_PROFESSIONAL ?
+ IEC958_AES0_PRO_FS_44100 :
+ (IEC958_AES3_CON_FS_44100 << 24);
+ }
+ else {
+ trident->spdif_pcm_ctrl = 0x3d; // 32000 Hz
+ trident->spdif_pcm_bits |=
+ trident->spdif_bits & IEC958_AES0_PROFESSIONAL ?
+ IEC958_AES0_PRO_FS_32000 :
+ (IEC958_AES3_CON_FS_32000 << 24);
+ }
+ change = old_bits != trident->spdif_pcm_bits;
+ spin_unlock_irq(&trident->reg_lock);
+
+ if (change)
+ snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE, &trident->spdif_pcm_ctl->id);
+
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_spdif_prepare
+
+ Description: Prepare SPDIF device for playback.
+
+ Parameters: substream - PCM substream class
+
+ Returns: Error status
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_trident_voice *voice = runtime->private_data;
+ struct snd_trident_voice *evoice = voice->extra;
+ struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[substream->number];
+ unsigned int RESO, LBAO;
+ unsigned int temp;
+
+ spin_lock_irq(&trident->reg_lock);
+
+ if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+
+ /* set delta (rate) value */
+ voice->Delta = snd_trident_convert_rate(runtime->rate);
+ voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
+
+ /* set Loop Back Address */
+ LBAO = runtime->dma_addr;
+ if (voice->memblk)
+ voice->LBA = voice->memblk->offset;
+ else
+ voice->LBA = LBAO;
+
+ voice->isync = 1;
+ voice->isync3 = 1;
+ voice->isync_mark = runtime->period_size;
+ voice->isync_max = runtime->buffer_size;
+
+ /* set target ESO for channel */
+ RESO = runtime->buffer_size - 1;
+ voice->ESO = voice->isync_ESO = (runtime->period_size * 2) + 6 - 1;
+
+ /* set ctrl mode */
+ voice->CTRL = snd_trident_control_mode(substream);
+
+ voice->FMC = 3;
+ voice->RVol = 0x7f;
+ voice->CVol = 0x7f;
+ voice->GVSel = 1;
+ voice->Pan = 0x7f;
+ voice->Vol = 0x3ff;
+ voice->EC = 0;
+ voice->CSO = 0;
+ voice->Alpha = 0;
+ voice->FMS = 0;
+ voice->Attribute = 0;
+
+ /* prepare surrogate IRQ channel */
+ snd_trident_write_voice_regs(trident, voice);
+
+ outw((RESO & 0xffff), TRID_REG(trident, NX_SPESO));
+ outb((RESO >> 16), TRID_REG(trident, NX_SPESO + 2));
+ outl((LBAO & 0xfffffffc), TRID_REG(trident, NX_SPLBA));
+ outw((voice->CSO & 0xffff), TRID_REG(trident, NX_SPCTRL_SPCSO));
+ outb((voice->CSO >> 16), TRID_REG(trident, NX_SPCTRL_SPCSO + 2));
+
+ /* set SPDIF setting */
+ outb(trident->spdif_pcm_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+ outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS));
+
+ } else { /* SiS */
+
+ /* set delta (rate) value */
+ voice->Delta = 0x800;
+ voice->spurious_threshold = snd_trident_spurious_threshold(48000, runtime->period_size);
+
+ /* set Loop Begin Address */
+ if (voice->memblk)
+ voice->LBA = voice->memblk->offset;
+ else
+ voice->LBA = runtime->dma_addr;
+
+ voice->CSO = 0;
+ voice->ESO = runtime->buffer_size - 1; /* in samples */
+ voice->CTRL = snd_trident_control_mode(substream);
+ voice->FMC = 3;
+ voice->GVSel = 1;
+ voice->EC = 0;
+ voice->Alpha = 0;
+ voice->FMS = 0;
+ voice->Vol = mix->vol;
+ voice->RVol = mix->rvol;
+ voice->CVol = mix->cvol;
+ voice->Pan = mix->pan;
+ voice->Attribute = (1<<(30-16))|(7<<(26-16))|
+ (0<<(24-16))|(0<<(19-16));
+
+ snd_trident_write_voice_regs(trident, voice);
+
+ if (evoice != NULL) {
+ evoice->Delta = voice->Delta;
+ evoice->spurious_threshold = voice->spurious_threshold;
+ evoice->LBA = voice->LBA;
+ evoice->CSO = 0;
+ evoice->ESO = (runtime->period_size * 2) + 4 - 1; /* in samples */
+ evoice->CTRL = voice->CTRL;
+ evoice->FMC = 3;
+ evoice->GVSel = trident->device == TRIDENT_DEVICE_ID_SI7018 ? 0 : 1;
+ evoice->EC = 0;
+ evoice->Alpha = 0;
+ evoice->FMS = 0;
+ evoice->Vol = 0x3ff; /* mute */
+ evoice->RVol = evoice->CVol = 0x7f; /* mute */
+ evoice->Pan = 0x7f; /* mute */
+ evoice->Attribute = 0;
+ snd_trident_write_voice_regs(trident, evoice);
+ evoice->isync2 = 1;
+ evoice->isync_mark = runtime->period_size;
+ evoice->ESO = (runtime->period_size * 2) - 1;
+ }
+
+ outl(trident->spdif_pcm_bits, TRID_REG(trident, SI_SPDIF_CS));
+ temp = inl(TRID_REG(trident, T4D_LFO_GC_CIR));
+ temp &= ~(1<<19);
+ outl(temp, TRID_REG(trident, T4D_LFO_GC_CIR));
+ temp = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+ temp |= SPDIF_EN;
+ outl(temp, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+ }
+
+ spin_unlock_irq(&trident->reg_lock);
+
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_trigger
+
+ Description: Start/stop devices
+
+ Parameters: substream - PCM substream class
+ cmd - trigger command (STOP, GO)
+
+ Returns: Error status
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_pcm_substream *s;
+ unsigned int what, whati, capture_flag, spdif_flag;
+ struct snd_trident_voice *voice, *evoice;
+ unsigned int val, go;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ go = 1;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ go = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ what = whati = capture_flag = spdif_flag = 0;
+ spin_lock(&trident->reg_lock);
+ val = inl(TRID_REG(trident, T4D_STIMER)) & 0x00ffffff;
+ snd_pcm_group_for_each_entry(s, substream) {
+ if ((struct snd_trident *) snd_pcm_substream_chip(s) == trident) {
+ voice = s->runtime->private_data;
+ evoice = voice->extra;
+ what |= 1 << (voice->number & 0x1f);
+ if (evoice == NULL) {
+ whati |= 1 << (voice->number & 0x1f);
+ } else {
+ what |= 1 << (evoice->number & 0x1f);
+ whati |= 1 << (evoice->number & 0x1f);
+ if (go)
+ evoice->stimer = val;
+ }
+ if (go) {
+ voice->running = 1;
+ voice->stimer = val;
+ } else {
+ voice->running = 0;
+ }
+ snd_pcm_trigger_done(s, substream);
+ if (voice->capture)
+ capture_flag = 1;
+ if (voice->spdif)
+ spdif_flag = 1;
+ }
+ }
+ if (spdif_flag) {
+ if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+ outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS));
+ val = trident->spdif_pcm_ctrl;
+ if (!go)
+ val &= ~(0x28);
+ outb(val, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+ } else {
+ outl(trident->spdif_pcm_bits, TRID_REG(trident, SI_SPDIF_CS));
+ val = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) | SPDIF_EN;
+ outl(val, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+ }
+ }
+ if (!go)
+ outl(what, TRID_REG(trident, T4D_STOP_B));
+ val = inl(TRID_REG(trident, T4D_AINTEN_B));
+ if (go) {
+ val |= whati;
+ } else {
+ val &= ~whati;
+ }
+ outl(val, TRID_REG(trident, T4D_AINTEN_B));
+ if (go) {
+ outl(what, TRID_REG(trident, T4D_START_B));
+
+ if (capture_flag && trident->device != TRIDENT_DEVICE_ID_SI7018)
+ outb(trident->bDMAStart, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD));
+ } else {
+ if (capture_flag && trident->device != TRIDENT_DEVICE_ID_SI7018)
+ outb(0x00, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD));
+ }
+ spin_unlock(&trident->reg_lock);
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_playback_pointer
+
+ Description: This routine return the playback position
+
+ Parameters: substream - PCM substream class
+
+ Returns: position of buffer
+
+ ---------------------------------------------------------------------------*/
+
+static snd_pcm_uframes_t snd_trident_playback_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_trident_voice *voice = runtime->private_data;
+ unsigned int cso;
+
+ if (!voice->running)
+ return 0;
+
+ spin_lock(&trident->reg_lock);
+
+ outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+
+ if (trident->device != TRIDENT_DEVICE_ID_NX) {
+ cso = inw(TRID_REG(trident, CH_DX_CSO_ALPHA_FMS + 2));
+ } else { // ID_4DWAVE_NX
+ cso = (unsigned int) inl(TRID_REG(trident, CH_NX_DELTA_CSO)) & 0x00ffffff;
+ }
+
+ spin_unlock(&trident->reg_lock);
+
+ if (cso >= runtime->buffer_size)
+ cso = 0;
+
+ return cso;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_capture_pointer
+
+ Description: This routine return the capture position
+
+ Parameters: pcm1 - PCM device class
+
+ Returns: position of buffer
+
+ ---------------------------------------------------------------------------*/
+
+static snd_pcm_uframes_t snd_trident_capture_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_trident_voice *voice = runtime->private_data;
+ unsigned int result;
+
+ if (!voice->running)
+ return 0;
+
+ result = inw(TRID_REG(trident, T4D_SBBL_SBCL));
+ if (runtime->channels > 1)
+ result >>= 1;
+ if (result > 0)
+ result = runtime->buffer_size - result;
+
+ return result;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_spdif_pointer
+
+ Description: This routine return the SPDIF playback position
+
+ Parameters: substream - PCM substream class
+
+ Returns: position of buffer
+
+ ---------------------------------------------------------------------------*/
+
+static snd_pcm_uframes_t snd_trident_spdif_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_trident_voice *voice = runtime->private_data;
+ unsigned int result;
+
+ if (!voice->running)
+ return 0;
+
+ result = inl(TRID_REG(trident, NX_SPCTRL_SPCSO)) & 0x00ffffff;
+
+ return result;
+}
+
+/*
+ * Playback support device description
+ */
+
+static const struct snd_pcm_hardware snd_trident_playback =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
+ SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */),
+ .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U16_LE),
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 4000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (256*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (256*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+/*
+ * Capture support device description
+ */
+
+static const struct snd_pcm_hardware snd_trident_capture =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
+ SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */),
+ .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U16_LE),
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 4000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+/*
+ * Foldback capture support device description
+ */
+
+static const struct snd_pcm_hardware snd_trident_foldback =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
+ SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+/*
+ * SPDIF playback support device description
+ */
+
+static const struct snd_pcm_hardware snd_trident_spdif =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
+ SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000),
+ .rate_min = 32000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware snd_trident_spdif_7018 =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
+ SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static void snd_trident_pcm_free_substream(struct snd_pcm_runtime *runtime)
+{
+ struct snd_trident_voice *voice = runtime->private_data;
+ struct snd_trident *trident;
+
+ if (voice) {
+ trident = voice->trident;
+ snd_trident_free_voice(trident, voice);
+ }
+}
+
+static int snd_trident_playback_open(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_trident_voice *voice;
+
+ voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
+ if (voice == NULL)
+ return -EAGAIN;
+ snd_trident_pcm_mixer_build(trident, voice, substream);
+ voice->substream = substream;
+ runtime->private_data = voice;
+ runtime->private_free = snd_trident_pcm_free_substream;
+ runtime->hw = snd_trident_playback;
+ snd_pcm_set_sync(substream);
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_playback_close
+
+ Description: This routine will close the 4DWave playback device. For now
+ we will simply free the dma transfer buffer.
+
+ Parameters: substream - PCM substream class
+
+ ---------------------------------------------------------------------------*/
+static int snd_trident_playback_close(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_trident_voice *voice = runtime->private_data;
+
+ snd_trident_pcm_mixer_free(trident, voice, substream);
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_spdif_open
+
+ Description: This routine will open the 4DWave SPDIF device.
+
+ Parameters: substream - PCM substream class
+
+ Returns: status - success or failure flag
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_open(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_trident_voice *voice;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
+ if (voice == NULL)
+ return -EAGAIN;
+ voice->spdif = 1;
+ voice->substream = substream;
+ spin_lock_irq(&trident->reg_lock);
+ trident->spdif_pcm_bits = trident->spdif_bits;
+ spin_unlock_irq(&trident->reg_lock);
+
+ runtime->private_data = voice;
+ runtime->private_free = snd_trident_pcm_free_substream;
+ if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+ runtime->hw = snd_trident_spdif;
+ } else {
+ runtime->hw = snd_trident_spdif_7018;
+ }
+
+ trident->spdif_pcm_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+ snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE |
+ SNDRV_CTL_EVENT_MASK_INFO, &trident->spdif_pcm_ctl->id);
+
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------------
+ snd_trident_spdif_close
+
+ Description: This routine will close the 4DWave SPDIF device.
+
+ Parameters: substream - PCM substream class
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_close(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ unsigned int temp;
+
+ spin_lock_irq(&trident->reg_lock);
+ // restore default SPDIF setting
+ if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+ outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+ outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
+ } else {
+ outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+ temp = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+ if (trident->spdif_ctrl) {
+ temp |= SPDIF_EN;
+ } else {
+ temp &= ~SPDIF_EN;
+ }
+ outl(temp, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+ }
+ spin_unlock_irq(&trident->reg_lock);
+ trident->spdif_pcm_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+ snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE |
+ SNDRV_CTL_EVENT_MASK_INFO, &trident->spdif_pcm_ctl->id);
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_capture_open
+
+ Description: This routine will open the 4DWave capture device.
+
+ Parameters: substream - PCM substream class
+
+ Returns: status - success or failure flag
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_capture_open(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_trident_voice *voice;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
+ if (voice == NULL)
+ return -EAGAIN;
+ voice->capture = 1;
+ voice->substream = substream;
+ runtime->private_data = voice;
+ runtime->private_free = snd_trident_pcm_free_substream;
+ runtime->hw = snd_trident_capture;
+ snd_pcm_set_sync(substream);
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_capture_close
+
+ Description: This routine will close the 4DWave capture device. For now
+ we will simply free the dma transfer buffer.
+
+ Parameters: substream - PCM substream class
+
+ ---------------------------------------------------------------------------*/
+static int snd_trident_capture_close(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_foldback_open
+
+ Description: This routine will open the 4DWave foldback capture device.
+
+ Parameters: substream - PCM substream class
+
+ Returns: status - success or failure flag
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_foldback_open(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_trident_voice *voice;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
+ if (voice == NULL)
+ return -EAGAIN;
+ voice->foldback_chan = substream->number;
+ voice->substream = substream;
+ runtime->private_data = voice;
+ runtime->private_free = snd_trident_pcm_free_substream;
+ runtime->hw = snd_trident_foldback;
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_foldback_close
+
+ Description: This routine will close the 4DWave foldback capture device.
+ For now we will simply free the dma transfer buffer.
+
+ Parameters: substream - PCM substream class
+
+ ---------------------------------------------------------------------------*/
+static int snd_trident_foldback_close(struct snd_pcm_substream *substream)
+{
+ struct snd_trident *trident = snd_pcm_substream_chip(substream);
+ struct snd_trident_voice *voice;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ voice = runtime->private_data;
+
+ /* stop capture channel */
+ spin_lock_irq(&trident->reg_lock);
+ outb(0x00, TRID_REG(trident, T4D_RCI + voice->foldback_chan));
+ spin_unlock_irq(&trident->reg_lock);
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ PCM operations
+ ---------------------------------------------------------------------------*/
+
+static const struct snd_pcm_ops snd_trident_playback_ops = {
+ .open = snd_trident_playback_open,
+ .close = snd_trident_playback_close,
+ .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 const struct snd_pcm_ops snd_trident_nx_playback_ops = {
+ .open = snd_trident_playback_open,
+ .close = snd_trident_playback_close,
+ .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 const struct snd_pcm_ops snd_trident_capture_ops = {
+ .open = snd_trident_capture_open,
+ .close = snd_trident_capture_close,
+ .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 const struct snd_pcm_ops snd_trident_si7018_capture_ops = {
+ .open = snd_trident_capture_open,
+ .close = snd_trident_capture_close,
+ .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 const struct snd_pcm_ops snd_trident_foldback_ops = {
+ .open = snd_trident_foldback_open,
+ .close = snd_trident_foldback_close,
+ .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 const struct snd_pcm_ops snd_trident_nx_foldback_ops = {
+ .open = snd_trident_foldback_open,
+ .close = snd_trident_foldback_close,
+ .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 const struct snd_pcm_ops snd_trident_spdif_ops = {
+ .open = snd_trident_spdif_open,
+ .close = snd_trident_spdif_close,
+ .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 const struct snd_pcm_ops snd_trident_spdif_7018_ops = {
+ .open = snd_trident_spdif_open,
+ .close = snd_trident_spdif_close,
+ .hw_params = snd_trident_spdif_hw_params,
+ .hw_free = snd_trident_hw_free,
+ .prepare = snd_trident_spdif_prepare,
+ .trigger = snd_trident_trigger,
+ .pointer = snd_trident_playback_pointer,
+};
+
+/*---------------------------------------------------------------------------
+ snd_trident_pcm
+
+ Description: This routine registers the 4DWave device for PCM support.
+
+ Parameters: trident - pointer to target device class for 4DWave.
+
+ Returns: None
+
+ ---------------------------------------------------------------------------*/
+
+int snd_trident_pcm(struct snd_trident *trident, int device)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(trident->card, "trident_dx_nx", device, trident->ChanPCM, 1, &pcm);
+ if (err < 0)
+ return err;
+
+ pcm->private_data = trident;
+
+ if (trident->tlb.entries) {
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_nx_playback_ops);
+ } else {
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_playback_ops);
+ }
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ trident->device != TRIDENT_DEVICE_ID_SI7018 ?
+ &snd_trident_capture_ops :
+ &snd_trident_si7018_capture_ops);
+
+ pcm->info_flags = 0;
+ pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+ strcpy(pcm->name, "Trident 4DWave");
+ trident->pcm = pcm;
+
+ if (trident->tlb.entries) {
+ struct snd_pcm_substream *substream;
+ for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next)
+ snd_pcm_set_managed_buffer(substream, SNDRV_DMA_TYPE_DEV_SG,
+ &trident->pci->dev,
+ 64*1024, 128*1024);
+ snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+ SNDRV_DMA_TYPE_DEV,
+ &trident->pci->dev,
+ 64*1024, 128*1024);
+ } else {
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ &trident->pci->dev,
+ 64*1024, 128*1024);
+ }
+
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_foldback_pcm
+
+ Description: This routine registers the 4DWave device for foldback PCM support.
+
+ Parameters: trident - pointer to target device class for 4DWave.
+
+ Returns: None
+
+ ---------------------------------------------------------------------------*/
+
+int snd_trident_foldback_pcm(struct snd_trident *trident, int device)
+{
+ struct snd_pcm *foldback;
+ int err;
+ int num_chan = 3;
+ struct snd_pcm_substream *substream;
+
+ if (trident->device == TRIDENT_DEVICE_ID_NX)
+ num_chan = 4;
+ err = snd_pcm_new(trident->card, "trident_dx_nx", device, 0, num_chan, &foldback);
+ if (err < 0)
+ return err;
+
+ foldback->private_data = trident;
+ if (trident->tlb.entries)
+ snd_pcm_set_ops(foldback, SNDRV_PCM_STREAM_CAPTURE, &snd_trident_nx_foldback_ops);
+ else
+ snd_pcm_set_ops(foldback, SNDRV_PCM_STREAM_CAPTURE, &snd_trident_foldback_ops);
+ foldback->info_flags = 0;
+ strcpy(foldback->name, "Trident 4DWave");
+ substream = foldback->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+ strcpy(substream->name, "Front Mixer");
+ substream = substream->next;
+ strcpy(substream->name, "Reverb Mixer");
+ substream = substream->next;
+ strcpy(substream->name, "Chorus Mixer");
+ if (num_chan == 4) {
+ substream = substream->next;
+ strcpy(substream->name, "Second AC'97 ADC");
+ }
+ trident->foldback = foldback;
+
+ if (trident->tlb.entries)
+ snd_pcm_set_managed_buffer_all(foldback, SNDRV_DMA_TYPE_DEV_SG,
+ &trident->pci->dev,
+ 0, 128*1024);
+ else
+ snd_pcm_set_managed_buffer_all(foldback, SNDRV_DMA_TYPE_DEV,
+ &trident->pci->dev,
+ 64*1024, 128*1024);
+
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_spdif
+
+ Description: This routine registers the 4DWave-NX device for SPDIF support.
+
+ Parameters: trident - pointer to target device class for 4DWave-NX.
+
+ Returns: None
+
+ ---------------------------------------------------------------------------*/
+
+int snd_trident_spdif_pcm(struct snd_trident *trident, int device)
+{
+ struct snd_pcm *spdif;
+ int err;
+
+ err = snd_pcm_new(trident->card, "trident_dx_nx IEC958", device, 1, 0, &spdif);
+ if (err < 0)
+ return err;
+
+ spdif->private_data = trident;
+ if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+ snd_pcm_set_ops(spdif, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_spdif_ops);
+ } else {
+ snd_pcm_set_ops(spdif, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_spdif_7018_ops);
+ }
+ spdif->info_flags = 0;
+ strcpy(spdif->name, "Trident 4DWave IEC958");
+ trident->spdif = spdif;
+
+ snd_pcm_set_managed_buffer_all(spdif, SNDRV_DMA_TYPE_DEV,
+ &trident->pci->dev, 64*1024, 128*1024);
+
+ return 0;
+}
+
+/*
+ * Mixer part
+ */
+
+
+/*---------------------------------------------------------------------------
+ snd_trident_spdif_control
+
+ Description: enable/disable S/PDIF out from ac97 mixer
+ ---------------------------------------------------------------------------*/
+
+#define snd_trident_spdif_control_info snd_ctl_boolean_mono_info
+
+static int snd_trident_spdif_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ unsigned char val;
+
+ spin_lock_irq(&trident->reg_lock);
+ val = trident->spdif_ctrl;
+ ucontrol->value.integer.value[0] = val == kcontrol->private_value;
+ spin_unlock_irq(&trident->reg_lock);
+ return 0;
+}
+
+static int snd_trident_spdif_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ unsigned char val;
+ int change;
+
+ val = ucontrol->value.integer.value[0] ? (unsigned char) kcontrol->private_value : 0x00;
+ spin_lock_irq(&trident->reg_lock);
+ /* S/PDIF C Channel bits 0-31 : 48khz, SCMS disabled */
+ change = trident->spdif_ctrl != val;
+ trident->spdif_ctrl = val;
+ if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+ if ((inb(TRID_REG(trident, NX_SPCTRL_SPCSO + 3)) & 0x10) == 0) {
+ outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
+ outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+ }
+ } else {
+ if (trident->spdif == NULL) {
+ unsigned int temp;
+ outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+ temp = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & ~SPDIF_EN;
+ if (val)
+ temp |= SPDIF_EN;
+ outl(temp, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+ }
+ }
+ spin_unlock_irq(&trident->reg_lock);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_trident_spdif_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH),
+ .info = snd_trident_spdif_control_info,
+ .get = snd_trident_spdif_control_get,
+ .put = snd_trident_spdif_control_put,
+ .private_value = 0x28,
+};
+
+/*---------------------------------------------------------------------------
+ snd_trident_spdif_default
+
+ Description: put/get the S/PDIF default settings
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_default_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+static int snd_trident_spdif_default_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+
+ spin_lock_irq(&trident->reg_lock);
+ ucontrol->value.iec958.status[0] = (trident->spdif_bits >> 0) & 0xff;
+ ucontrol->value.iec958.status[1] = (trident->spdif_bits >> 8) & 0xff;
+ ucontrol->value.iec958.status[2] = (trident->spdif_bits >> 16) & 0xff;
+ ucontrol->value.iec958.status[3] = (trident->spdif_bits >> 24) & 0xff;
+ spin_unlock_irq(&trident->reg_lock);
+ return 0;
+}
+
+static int snd_trident_spdif_default_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ unsigned int val;
+ int change;
+
+ val = (ucontrol->value.iec958.status[0] << 0) |
+ (ucontrol->value.iec958.status[1] << 8) |
+ (ucontrol->value.iec958.status[2] << 16) |
+ (ucontrol->value.iec958.status[3] << 24);
+ spin_lock_irq(&trident->reg_lock);
+ change = trident->spdif_bits != val;
+ trident->spdif_bits = val;
+ if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+ if ((inb(TRID_REG(trident, NX_SPCTRL_SPCSO + 3)) & 0x10) == 0)
+ outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
+ } else {
+ if (trident->spdif == NULL)
+ outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+ }
+ spin_unlock_irq(&trident->reg_lock);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_trident_spdif_default =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+ .info = snd_trident_spdif_default_info,
+ .get = snd_trident_spdif_default_get,
+ .put = snd_trident_spdif_default_put
+};
+
+/*---------------------------------------------------------------------------
+ snd_trident_spdif_mask
+
+ Description: put/get the S/PDIF mask
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_mask_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+static int snd_trident_spdif_mask_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.iec958.status[0] = 0xff;
+ ucontrol->value.iec958.status[1] = 0xff;
+ ucontrol->value.iec958.status[2] = 0xff;
+ ucontrol->value.iec958.status[3] = 0xff;
+ return 0;
+}
+
+static const struct snd_kcontrol_new snd_trident_spdif_mask =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
+ .info = snd_trident_spdif_mask_info,
+ .get = snd_trident_spdif_mask_get,
+};
+
+/*---------------------------------------------------------------------------
+ snd_trident_spdif_stream
+
+ Description: put/get the S/PDIF stream settings
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_stream_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+static int snd_trident_spdif_stream_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+
+ spin_lock_irq(&trident->reg_lock);
+ ucontrol->value.iec958.status[0] = (trident->spdif_pcm_bits >> 0) & 0xff;
+ ucontrol->value.iec958.status[1] = (trident->spdif_pcm_bits >> 8) & 0xff;
+ ucontrol->value.iec958.status[2] = (trident->spdif_pcm_bits >> 16) & 0xff;
+ ucontrol->value.iec958.status[3] = (trident->spdif_pcm_bits >> 24) & 0xff;
+ spin_unlock_irq(&trident->reg_lock);
+ return 0;
+}
+
+static int snd_trident_spdif_stream_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ unsigned int val;
+ int change;
+
+ val = (ucontrol->value.iec958.status[0] << 0) |
+ (ucontrol->value.iec958.status[1] << 8) |
+ (ucontrol->value.iec958.status[2] << 16) |
+ (ucontrol->value.iec958.status[3] << 24);
+ spin_lock_irq(&trident->reg_lock);
+ change = trident->spdif_pcm_bits != val;
+ trident->spdif_pcm_bits = val;
+ if (trident->spdif != NULL) {
+ if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+ outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS));
+ } else {
+ outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+ }
+ }
+ spin_unlock_irq(&trident->reg_lock);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_trident_spdif_stream =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM),
+ .info = snd_trident_spdif_stream_info,
+ .get = snd_trident_spdif_stream_get,
+ .put = snd_trident_spdif_stream_put
+};
+
+/*---------------------------------------------------------------------------
+ snd_trident_ac97_control
+
+ Description: enable/disable rear path for ac97
+ ---------------------------------------------------------------------------*/
+
+#define snd_trident_ac97_control_info snd_ctl_boolean_mono_info
+
+static int snd_trident_ac97_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ unsigned char val;
+
+ spin_lock_irq(&trident->reg_lock);
+ val = trident->ac97_ctrl = inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+ ucontrol->value.integer.value[0] = (val & (1 << kcontrol->private_value)) ? 1 : 0;
+ spin_unlock_irq(&trident->reg_lock);
+ return 0;
+}
+
+static int snd_trident_ac97_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ unsigned char val;
+ int change = 0;
+
+ spin_lock_irq(&trident->reg_lock);
+ val = trident->ac97_ctrl = inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+ val &= ~(1 << kcontrol->private_value);
+ if (ucontrol->value.integer.value[0])
+ val |= 1 << kcontrol->private_value;
+ change = val != trident->ac97_ctrl;
+ trident->ac97_ctrl = val;
+ outl(trident->ac97_ctrl = val, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+ spin_unlock_irq(&trident->reg_lock);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_trident_ac97_rear_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Rear Path",
+ .info = snd_trident_ac97_control_info,
+ .get = snd_trident_ac97_control_get,
+ .put = snd_trident_ac97_control_put,
+ .private_value = 4,
+};
+
+/*---------------------------------------------------------------------------
+ snd_trident_vol_control
+
+ Description: wave & music volume control
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_vol_control_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 255;
+ return 0;
+}
+
+static int snd_trident_vol_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ unsigned int val;
+
+ val = trident->musicvol_wavevol;
+ ucontrol->value.integer.value[0] = 255 - ((val >> kcontrol->private_value) & 0xff);
+ ucontrol->value.integer.value[1] = 255 - ((val >> (kcontrol->private_value + 8)) & 0xff);
+ return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_gvol, -6375, 25, 0);
+
+static int snd_trident_vol_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ unsigned int val;
+ int change = 0;
+
+ spin_lock_irq(&trident->reg_lock);
+ val = trident->musicvol_wavevol;
+ val &= ~(0xffff << kcontrol->private_value);
+ val |= ((255 - (ucontrol->value.integer.value[0] & 0xff)) |
+ ((255 - (ucontrol->value.integer.value[1] & 0xff)) << 8)) << kcontrol->private_value;
+ change = val != trident->musicvol_wavevol;
+ outl(trident->musicvol_wavevol = val, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
+ spin_unlock_irq(&trident->reg_lock);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_trident_vol_music_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Music Playback Volume",
+ .info = snd_trident_vol_control_info,
+ .get = snd_trident_vol_control_get,
+ .put = snd_trident_vol_control_put,
+ .private_value = 16,
+ .tlv = { .p = db_scale_gvol },
+};
+
+static const struct snd_kcontrol_new snd_trident_vol_wave_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Wave Playback Volume",
+ .info = snd_trident_vol_control_info,
+ .get = snd_trident_vol_control_get,
+ .put = snd_trident_vol_control_put,
+ .private_value = 0,
+ .tlv = { .p = db_scale_gvol },
+};
+
+/*---------------------------------------------------------------------------
+ snd_trident_pcm_vol_control
+
+ Description: PCM front volume control
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_pcm_vol_control_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 255;
+ if (trident->device == TRIDENT_DEVICE_ID_SI7018)
+ uinfo->value.integer.max = 1023;
+ return 0;
+}
+
+static int snd_trident_pcm_vol_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+
+ if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+ ucontrol->value.integer.value[0] = 1023 - mix->vol;
+ } else {
+ ucontrol->value.integer.value[0] = 255 - (mix->vol>>2);
+ }
+ return 0;
+}
+
+static int snd_trident_pcm_vol_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+ unsigned int val;
+ int change = 0;
+
+ if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+ val = 1023 - (ucontrol->value.integer.value[0] & 1023);
+ } else {
+ val = (255 - (ucontrol->value.integer.value[0] & 255)) << 2;
+ }
+ spin_lock_irq(&trident->reg_lock);
+ change = val != mix->vol;
+ mix->vol = val;
+ if (mix->voice != NULL)
+ snd_trident_write_vol_reg(trident, mix->voice, val);
+ spin_unlock_irq(&trident->reg_lock);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_trident_pcm_vol_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Front Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+ .count = 32,
+ .info = snd_trident_pcm_vol_control_info,
+ .get = snd_trident_pcm_vol_control_get,
+ .put = snd_trident_pcm_vol_control_put,
+ /* FIXME: no tlv yet */
+};
+
+/*---------------------------------------------------------------------------
+ snd_trident_pcm_pan_control
+
+ Description: PCM front pan control
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_pcm_pan_control_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 127;
+ return 0;
+}
+
+static int snd_trident_pcm_pan_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+
+ ucontrol->value.integer.value[0] = mix->pan;
+ if (ucontrol->value.integer.value[0] & 0x40) {
+ ucontrol->value.integer.value[0] = (0x3f - (ucontrol->value.integer.value[0] & 0x3f));
+ } else {
+ ucontrol->value.integer.value[0] |= 0x40;
+ }
+ return 0;
+}
+
+static int snd_trident_pcm_pan_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+ unsigned char val;
+ int change = 0;
+
+ if (ucontrol->value.integer.value[0] & 0x40)
+ val = ucontrol->value.integer.value[0] & 0x3f;
+ else
+ val = (0x3f - (ucontrol->value.integer.value[0] & 0x3f)) | 0x40;
+ spin_lock_irq(&trident->reg_lock);
+ change = val != mix->pan;
+ mix->pan = val;
+ if (mix->voice != NULL)
+ snd_trident_write_pan_reg(trident, mix->voice, val);
+ spin_unlock_irq(&trident->reg_lock);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_trident_pcm_pan_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Pan Playback Control",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+ .count = 32,
+ .info = snd_trident_pcm_pan_control_info,
+ .get = snd_trident_pcm_pan_control_get,
+ .put = snd_trident_pcm_pan_control_put,
+};
+
+/*---------------------------------------------------------------------------
+ snd_trident_pcm_rvol_control
+
+ Description: PCM reverb volume control
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_pcm_rvol_control_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 127;
+ return 0;
+}
+
+static int snd_trident_pcm_rvol_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+
+ ucontrol->value.integer.value[0] = 127 - mix->rvol;
+ return 0;
+}
+
+static int snd_trident_pcm_rvol_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+ unsigned short val;
+ int change = 0;
+
+ val = 0x7f - (ucontrol->value.integer.value[0] & 0x7f);
+ spin_lock_irq(&trident->reg_lock);
+ change = val != mix->rvol;
+ mix->rvol = val;
+ if (mix->voice != NULL)
+ snd_trident_write_rvol_reg(trident, mix->voice, val);
+ spin_unlock_irq(&trident->reg_lock);
+ return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_crvol, -3175, 25, 1);
+
+static const struct snd_kcontrol_new snd_trident_pcm_rvol_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Reverb Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+ .count = 32,
+ .info = snd_trident_pcm_rvol_control_info,
+ .get = snd_trident_pcm_rvol_control_get,
+ .put = snd_trident_pcm_rvol_control_put,
+ .tlv = { .p = db_scale_crvol },
+};
+
+/*---------------------------------------------------------------------------
+ snd_trident_pcm_cvol_control
+
+ Description: PCM chorus volume control
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_pcm_cvol_control_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 127;
+ return 0;
+}
+
+static int snd_trident_pcm_cvol_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+
+ ucontrol->value.integer.value[0] = 127 - mix->cvol;
+ return 0;
+}
+
+static int snd_trident_pcm_cvol_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+ struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+ unsigned short val;
+ int change = 0;
+
+ val = 0x7f - (ucontrol->value.integer.value[0] & 0x7f);
+ spin_lock_irq(&trident->reg_lock);
+ change = val != mix->cvol;
+ mix->cvol = val;
+ if (mix->voice != NULL)
+ snd_trident_write_cvol_reg(trident, mix->voice, val);
+ spin_unlock_irq(&trident->reg_lock);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_trident_pcm_cvol_control =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Chorus Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+ .count = 32,
+ .info = snd_trident_pcm_cvol_control_info,
+ .get = snd_trident_pcm_cvol_control_get,
+ .put = snd_trident_pcm_cvol_control_put,
+ .tlv = { .p = db_scale_crvol },
+};
+
+static void snd_trident_notify_pcm_change1(struct snd_card *card,
+ struct snd_kcontrol *kctl,
+ int num, int activate)
+{
+ struct snd_ctl_elem_id id;
+
+ if (! kctl)
+ return;
+ if (activate)
+ kctl->vd[num].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+ else
+ kctl->vd[num].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE |
+ SNDRV_CTL_EVENT_MASK_INFO,
+ snd_ctl_build_ioff(&id, kctl, num));
+}
+
+static void snd_trident_notify_pcm_change(struct snd_trident *trident,
+ struct snd_trident_pcm_mixer *tmix,
+ int num, int activate)
+{
+ snd_trident_notify_pcm_change1(trident->card, trident->ctl_vol, num, activate);
+ snd_trident_notify_pcm_change1(trident->card, trident->ctl_pan, num, activate);
+ snd_trident_notify_pcm_change1(trident->card, trident->ctl_rvol, num, activate);
+ snd_trident_notify_pcm_change1(trident->card, trident->ctl_cvol, num, activate);
+}
+
+static int snd_trident_pcm_mixer_build(struct snd_trident *trident,
+ struct snd_trident_voice *voice,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_trident_pcm_mixer *tmix;
+
+ if (snd_BUG_ON(!trident || !voice || !substream))
+ return -EINVAL;
+ tmix = &trident->pcm_mixer[substream->number];
+ tmix->voice = voice;
+ tmix->vol = T4D_DEFAULT_PCM_VOL;
+ tmix->pan = T4D_DEFAULT_PCM_PAN;
+ tmix->rvol = T4D_DEFAULT_PCM_RVOL;
+ tmix->cvol = T4D_DEFAULT_PCM_CVOL;
+ snd_trident_notify_pcm_change(trident, tmix, substream->number, 1);
+ return 0;
+}
+
+static int snd_trident_pcm_mixer_free(struct snd_trident *trident, struct snd_trident_voice *voice, struct snd_pcm_substream *substream)
+{
+ struct snd_trident_pcm_mixer *tmix;
+
+ if (snd_BUG_ON(!trident || !substream))
+ return -EINVAL;
+ tmix = &trident->pcm_mixer[substream->number];
+ tmix->voice = NULL;
+ snd_trident_notify_pcm_change(trident, tmix, substream->number, 0);
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_mixer
+
+ Description: This routine registers the 4DWave device for mixer support.
+
+ Parameters: trident - pointer to target device class for 4DWave.
+
+ Returns: None
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_mixer(struct snd_trident *trident, int pcm_spdif_device)
+{
+ struct snd_ac97_template _ac97;
+ struct snd_card *card = trident->card;
+ struct snd_kcontrol *kctl;
+ struct snd_ctl_elem_value *uctl;
+ int idx, err, retries = 2;
+ static const struct snd_ac97_bus_ops ops = {
+ .write = snd_trident_codec_write,
+ .read = snd_trident_codec_read,
+ };
+
+ uctl = kzalloc(sizeof(*uctl), GFP_KERNEL);
+ if (!uctl)
+ return -ENOMEM;
+
+ err = snd_ac97_bus(trident->card, 0, &ops, NULL, &trident->ac97_bus);
+ if (err < 0)
+ goto __out;
+
+ memset(&_ac97, 0, sizeof(_ac97));
+ _ac97.private_data = trident;
+ trident->ac97_detect = 1;
+
+ __again:
+ err = snd_ac97_mixer(trident->ac97_bus, &_ac97, &trident->ac97);
+ if (err < 0) {
+ if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+ err = snd_trident_sis_reset(trident);
+ if (err < 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)
+ dev_err(trident->card->dev,
+ "SI7018: the secondary codec - invalid access\n");
+#if 0 // only for my testing purpose --jk
+ {
+ struct snd_ac97 *mc97;
+ err = snd_ac97_modem(trident->card, &_ac97, &mc97);
+ if (err < 0)
+ dev_err(trident->card->dev,
+ "snd_ac97_modem returned error %i\n", err);
+ }
+#endif
+ }
+
+ trident->ac97_detect = 0;
+
+ if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+ kctl = snd_ctl_new1(&snd_trident_vol_wave_control, trident);
+ err = snd_ctl_add(card, kctl);
+ if (err < 0)
+ goto __out;
+ kctl->put(kctl, uctl);
+ kctl = snd_ctl_new1(&snd_trident_vol_music_control, trident);
+ err = snd_ctl_add(card, kctl);
+ if (err < 0)
+ goto __out;
+ kctl->put(kctl, uctl);
+ outl(trident->musicvol_wavevol = 0x00000000, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
+ } else {
+ outl(trident->musicvol_wavevol = 0xffff0000, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
+ }
+
+ for (idx = 0; idx < 32; idx++) {
+ struct snd_trident_pcm_mixer *tmix;
+
+ tmix = &trident->pcm_mixer[idx];
+ tmix->voice = NULL;
+ }
+ trident->ctl_vol = snd_ctl_new1(&snd_trident_pcm_vol_control, trident);
+ if (!trident->ctl_vol)
+ goto __nomem;
+ err = snd_ctl_add(card, trident->ctl_vol);
+ if (err)
+ goto __out;
+
+ trident->ctl_pan = snd_ctl_new1(&snd_trident_pcm_pan_control, trident);
+ if (!trident->ctl_pan)
+ goto __nomem;
+ err = snd_ctl_add(card, trident->ctl_pan);
+ if (err)
+ goto __out;
+
+ trident->ctl_rvol = snd_ctl_new1(&snd_trident_pcm_rvol_control, trident);
+ if (!trident->ctl_rvol)
+ goto __nomem;
+ err = snd_ctl_add(card, trident->ctl_rvol);
+ if (err)
+ goto __out;
+
+ trident->ctl_cvol = snd_ctl_new1(&snd_trident_pcm_cvol_control, trident);
+ if (!trident->ctl_cvol)
+ goto __nomem;
+ err = snd_ctl_add(card, trident->ctl_cvol);
+ if (err)
+ goto __out;
+
+ if (trident->device == TRIDENT_DEVICE_ID_NX) {
+ kctl = snd_ctl_new1(&snd_trident_ac97_rear_control, trident);
+ err = snd_ctl_add(card, kctl);
+ if (err < 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;
+ err = snd_ctl_add(card, kctl);
+ if (err < 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;
+ err = snd_ctl_add(card, kctl);
+ if (err < 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;
+ err = snd_ctl_add(card, kctl);
+ if (err < 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;
+ err = snd_ctl_add(card, kctl);
+ if (err < 0)
+ goto __out;
+ trident->spdif_pcm_ctl = kctl;
+ }
+
+ err = 0;
+ goto __out;
+
+ __nomem:
+ err = -ENOMEM;
+
+ __out:
+ kfree(uctl);
+
+ return err;
+}
+
+/*
+ * gameport interface
+ */
+
+#if IS_REACHABLE(CONFIG_GAMEPORT)
+
+static unsigned char snd_trident_gameport_read(struct gameport *gameport)
+{
+ struct snd_trident *chip = gameport_get_port_data(gameport);
+
+ if (snd_BUG_ON(!chip))
+ return 0;
+ return inb(TRID_REG(chip, GAMEPORT_LEGACY));
+}
+
+static void snd_trident_gameport_trigger(struct gameport *gameport)
+{
+ struct snd_trident *chip = gameport_get_port_data(gameport);
+
+ if (snd_BUG_ON(!chip))
+ return;
+ outb(0xff, TRID_REG(chip, GAMEPORT_LEGACY));
+}
+
+static int snd_trident_gameport_cooked_read(struct gameport *gameport, int *axes, int *buttons)
+{
+ struct snd_trident *chip = gameport_get_port_data(gameport);
+ int i;
+
+ if (snd_BUG_ON(!chip))
+ return 0;
+
+ *buttons = (~inb(TRID_REG(chip, GAMEPORT_LEGACY)) >> 4) & 0xf;
+
+ for (i = 0; i < 4; i++) {
+ axes[i] = inw(TRID_REG(chip, GAMEPORT_AXES + i * 2));
+ if (axes[i] == 0xffff) axes[i] = -1;
+ }
+
+ return 0;
+}
+
+static int snd_trident_gameport_open(struct gameport *gameport, int mode)
+{
+ struct snd_trident *chip = gameport_get_port_data(gameport);
+
+ if (snd_BUG_ON(!chip))
+ return 0;
+
+ switch (mode) {
+ case GAMEPORT_MODE_COOKED:
+ outb(GAMEPORT_MODE_ADC, TRID_REG(chip, GAMEPORT_GCR));
+ msleep(20);
+ return 0;
+ case GAMEPORT_MODE_RAW:
+ outb(0, TRID_REG(chip, GAMEPORT_GCR));
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+int snd_trident_create_gameport(struct snd_trident *chip)
+{
+ struct gameport *gp;
+
+ chip->gameport = gp = gameport_allocate_port();
+ if (!gp) {
+ dev_err(chip->card->dev,
+ "cannot allocate memory for gameport\n");
+ return -ENOMEM;
+ }
+
+ gameport_set_name(gp, "Trident 4DWave");
+ gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci));
+ gameport_set_dev_parent(gp, &chip->pci->dev);
+
+ gameport_set_port_data(gp, chip);
+ gp->fuzz = 64;
+ gp->read = snd_trident_gameport_read;
+ gp->trigger = snd_trident_gameport_trigger;
+ gp->cooked_read = snd_trident_gameport_cooked_read;
+ gp->open = snd_trident_gameport_open;
+
+ gameport_register_port(gp);
+
+ return 0;
+}
+
+static inline void snd_trident_free_gameport(struct snd_trident *chip)
+{
+ if (chip->gameport) {
+ gameport_unregister_port(chip->gameport);
+ chip->gameport = NULL;
+ }
+}
+#else
+int snd_trident_create_gameport(struct snd_trident *chip) { return -ENOSYS; }
+static inline void snd_trident_free_gameport(struct snd_trident *chip) { }
+#endif /* CONFIG_GAMEPORT */
+
+/*
+ * delay for 1 tick
+ */
+static inline void do_delay(struct snd_trident *chip)
+{
+ schedule_timeout_uninterruptible(1);
+}
+
+/*
+ * SiS reset routine
+ */
+
+static int snd_trident_sis_reset(struct snd_trident *trident)
+{
+ unsigned long end_time;
+ unsigned int i;
+ int r;
+
+ r = trident->in_suspend ? 0 : 2; /* count of retries */
+ __si7018_retry:
+ pci_write_config_byte(trident->pci, 0x46, 0x04); /* SOFTWARE RESET */
+ udelay(100);
+ pci_write_config_byte(trident->pci, 0x46, 0x00);
+ udelay(100);
+ /* disable AC97 GPIO interrupt */
+ outb(0x00, TRID_REG(trident, SI_AC97_GPIO));
+ /* initialize serial interface, force cold reset */
+ i = PCMOUT|SURROUT|CENTEROUT|LFEOUT|SECONDARY_ID|COLD_RESET;
+ outl(i, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+ udelay(1000);
+ /* remove cold reset */
+ i &= ~COLD_RESET;
+ outl(i, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+ udelay(2000);
+ /* wait, until the codec is ready */
+ end_time = (jiffies + (HZ * 3) / 4) + 1;
+ do {
+ if ((inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & SI_AC97_PRIMARY_READY) != 0)
+ goto __si7018_ok;
+ do_delay(trident);
+ } while (time_after_eq(end_time, jiffies));
+ dev_err(trident->card->dev, "AC'97 codec ready error [0x%x]\n",
+ inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)));
+ if (r-- > 0) {
+ end_time = jiffies + HZ;
+ do {
+ do_delay(trident);
+ } while (time_after_eq(end_time, jiffies));
+ goto __si7018_retry;
+ }
+ __si7018_ok:
+ /* wait for the second codec */
+ do {
+ if ((inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & SI_AC97_SECONDARY_READY) != 0)
+ break;
+ do_delay(trident);
+ } while (time_after_eq(end_time, jiffies));
+ /* enable 64 channel mode */
+ outl(BANK_B_EN, TRID_REG(trident, T4D_LFO_GC_CIR));
+ return 0;
+}
+
+/*
+ * /proc interface
+ */
+
+static void snd_trident_proc_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_trident *trident = entry->private_data;
+ char *s;
+
+ switch (trident->device) {
+ case TRIDENT_DEVICE_ID_SI7018:
+ s = "SiS 7018 Audio";
+ break;
+ case TRIDENT_DEVICE_ID_DX:
+ s = "Trident 4DWave PCI DX";
+ break;
+ case TRIDENT_DEVICE_ID_NX:
+ s = "Trident 4DWave PCI NX";
+ break;
+ default:
+ s = "???";
+ }
+ snd_iprintf(buffer, "%s\n\n", s);
+ snd_iprintf(buffer, "Spurious IRQs : %d\n", trident->spurious_irq_count);
+ snd_iprintf(buffer, "Spurious IRQ dlta: %d\n", trident->spurious_irq_max_delta);
+ if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018)
+ snd_iprintf(buffer, "IEC958 Mixer Out : %s\n", trident->spdif_ctrl == 0x28 ? "on" : "off");
+ if (trident->device == TRIDENT_DEVICE_ID_NX) {
+ snd_iprintf(buffer, "Rear Speakers : %s\n", trident->ac97_ctrl & 0x00000010 ? "on" : "off");
+ if (trident->tlb.entries) {
+ snd_iprintf(buffer,"\nVirtual Memory\n");
+ snd_iprintf(buffer, "Memory Maximum : %d\n", trident->tlb.memhdr->size);
+ snd_iprintf(buffer, "Memory Used : %d\n", trident->tlb.memhdr->used);
+ snd_iprintf(buffer, "Memory Free : %d\n", snd_util_mem_avail(trident->tlb.memhdr));
+ }
+ }
+}
+
+static void snd_trident_proc_init(struct snd_trident *trident)
+{
+ const char *s = "trident";
+
+ if (trident->device == TRIDENT_DEVICE_ID_SI7018)
+ s = "sis7018";
+ snd_card_ro_proc_new(trident->card, s, trident, snd_trident_proc_read);
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_tlb_alloc
+
+ Description: Allocate and set up the TLB page table on 4D NX.
+ Each entry has 4 bytes (physical PCI address).
+
+ Parameters: trident - pointer to target device class for 4DWave.
+
+ Returns: 0 or negative error code
+
+ ---------------------------------------------------------------------------*/
+
+static int snd_trident_tlb_alloc(struct snd_trident *trident)
+{
+ int i;
+
+ /* TLB array must be aligned to 16kB !!! so we allocate
+ 32kB region and correct offset when necessary */
+
+ trident->tlb.buffer =
+ snd_devm_alloc_pages(&trident->pci->dev, SNDRV_DMA_TYPE_DEV,
+ 2 * SNDRV_TRIDENT_MAX_PAGES * 4);
+ if (!trident->tlb.buffer) {
+ dev_err(trident->card->dev, "unable to allocate TLB buffer\n");
+ return -ENOMEM;
+ }
+ trident->tlb.entries = (__le32 *)ALIGN((unsigned long)trident->tlb.buffer->area, SNDRV_TRIDENT_MAX_PAGES * 4);
+ trident->tlb.entries_dmaaddr = ALIGN(trident->tlb.buffer->addr, SNDRV_TRIDENT_MAX_PAGES * 4);
+
+ /* allocate and setup silent page and initialise TLB entries */
+ trident->tlb.silent_page =
+ snd_devm_alloc_pages(&trident->pci->dev, SNDRV_DMA_TYPE_DEV,
+ SNDRV_TRIDENT_PAGE_SIZE);
+ if (!trident->tlb.silent_page) {
+ dev_err(trident->card->dev, "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));
+
+ /* use emu memory block manager code to manage tlb page allocation */
+ trident->tlb.memhdr = snd_util_memhdr_new(SNDRV_TRIDENT_PAGE_SIZE * SNDRV_TRIDENT_MAX_PAGES);
+ if (trident->tlb.memhdr == NULL)
+ return -ENOMEM;
+
+ trident->tlb.memhdr->block_extra_size = sizeof(struct snd_trident_memblk_arg);
+ return 0;
+}
+
+/*
+ * initialize 4D DX chip
+ */
+
+static void snd_trident_stop_all_voices(struct snd_trident *trident)
+{
+ outl(0xffffffff, TRID_REG(trident, T4D_STOP_A));
+ outl(0xffffffff, TRID_REG(trident, T4D_STOP_B));
+ outl(0, TRID_REG(trident, T4D_AINTEN_A));
+ outl(0, TRID_REG(trident, T4D_AINTEN_B));
+}
+
+static int snd_trident_4d_dx_init(struct snd_trident *trident)
+{
+ struct pci_dev *pci = trident->pci;
+ unsigned long end_time;
+
+ /* reset the legacy configuration and whole audio/wavetable block */
+ pci_write_config_dword(pci, 0x40, 0); /* DDMA */
+ pci_write_config_byte(pci, 0x44, 0); /* ports */
+ pci_write_config_byte(pci, 0x45, 0); /* Legacy DMA */
+ pci_write_config_byte(pci, 0x46, 4); /* reset */
+ udelay(100);
+ pci_write_config_byte(pci, 0x46, 0); /* release reset */
+ udelay(100);
+
+ /* warm reset of the AC'97 codec */
+ outl(0x00000001, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
+ udelay(100);
+ outl(0x00000000, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
+ /* DAC on, disable SB IRQ and try to force ADC valid signal */
+ trident->ac97_ctrl = 0x0000004a;
+ outl(trident->ac97_ctrl, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
+ /* wait, until the codec is ready */
+ end_time = (jiffies + (HZ * 3) / 4) + 1;
+ do {
+ if ((inl(TRID_REG(trident, DX_ACR2_AC97_COM_STAT)) & 0x0010) != 0)
+ goto __dx_ok;
+ do_delay(trident);
+ } while (time_after_eq(end_time, jiffies));
+ dev_err(trident->card->dev, "AC'97 codec ready error\n");
+ return -EIO;
+
+ __dx_ok:
+ snd_trident_stop_all_voices(trident);
+
+ return 0;
+}
+
+/*
+ * initialize 4D NX chip
+ */
+static int snd_trident_4d_nx_init(struct snd_trident *trident)
+{
+ struct pci_dev *pci = trident->pci;
+ unsigned long end_time;
+
+ /* reset the legacy configuration and whole audio/wavetable block */
+ pci_write_config_dword(pci, 0x40, 0); /* DDMA */
+ pci_write_config_byte(pci, 0x44, 0); /* ports */
+ pci_write_config_byte(pci, 0x45, 0); /* Legacy DMA */
+
+ pci_write_config_byte(pci, 0x46, 1); /* reset */
+ udelay(100);
+ pci_write_config_byte(pci, 0x46, 0); /* release reset */
+ udelay(100);
+
+ /* warm reset of the AC'97 codec */
+ outl(0x00000001, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+ udelay(100);
+ outl(0x00000000, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+ /* wait, until the codec is ready */
+ end_time = (jiffies + (HZ * 3) / 4) + 1;
+ do {
+ if ((inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT)) & 0x0008) != 0)
+ goto __nx_ok;
+ do_delay(trident);
+ } while (time_after_eq(end_time, jiffies));
+ dev_err(trident->card->dev, "AC'97 codec ready error [0x%x]\n",
+ inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT)));
+ return -EIO;
+
+ __nx_ok:
+ /* DAC on */
+ trident->ac97_ctrl = 0x00000002;
+ outl(trident->ac97_ctrl, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+ /* disable SB IRQ */
+ outl(NX_SB_IRQ_DISABLE, TRID_REG(trident, T4D_MISCINT));
+
+ snd_trident_stop_all_voices(trident);
+
+ if (trident->tlb.entries != NULL) {
+ unsigned int i;
+ /* enable virtual addressing via TLB */
+ i = trident->tlb.entries_dmaaddr;
+ i |= 0x00000001;
+ outl(i, TRID_REG(trident, NX_TLBC));
+ } else {
+ outl(0, TRID_REG(trident, NX_TLBC));
+ }
+ /* initialize S/PDIF */
+ outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
+ outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+
+ return 0;
+}
+
+/*
+ * initialize sis7018 chip
+ */
+static int snd_trident_sis_init(struct snd_trident *trident)
+{
+ int err;
+
+ err = snd_trident_sis_reset(trident);
+ if (err < 0)
+ return err;
+
+ snd_trident_stop_all_voices(trident);
+
+ /* initialize S/PDIF */
+ outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_create
+
+ Description: This routine will create the device specific class for
+ the 4DWave card. It will also perform basic initialization.
+
+ Parameters: card - which card to create
+ pci - interface to PCI bus resource info
+ dma1ptr - playback dma buffer
+ dma2ptr - capture dma buffer
+ irqptr - interrupt resource info
+
+ Returns: 4DWave device class private data
+
+ ---------------------------------------------------------------------------*/
+
+int snd_trident_create(struct snd_card *card,
+ struct pci_dev *pci,
+ int pcm_streams,
+ int pcm_spdif_device,
+ int max_wavetable_size)
+{
+ struct snd_trident *trident = card->private_data;
+ int i, err;
+ struct snd_trident_voice *voice;
+ struct snd_trident_pcm_mixer *tmix;
+
+ /* enable PCI device */
+ err = pcim_enable_device(pci);
+ if (err < 0)
+ return err;
+ /* check, if we can restrict PCI DMA transfers to 30 bits */
+ if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(30))) {
+ dev_err(card->dev,
+ "architecture does not support 30bit PCI busmaster DMA\n");
+ return -ENXIO;
+ }
+
+ 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;
+ card->private_free = snd_trident_free;
+
+ trident->midi_port = TRID_REG(trident, T4D_MPU401_BASE);
+ pci_set_master(pci);
+
+ err = pci_request_regions(pci, "Trident Audio");
+ if (err < 0)
+ return err;
+ trident->port = pci_resource_start(pci, 0);
+
+ if (devm_request_irq(&pci->dev, pci->irq, snd_trident_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME, trident)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ return -EBUSY;
+ }
+ trident->irq = pci->irq;
+ card->sync_irq = trident->irq;
+
+ /* allocate 16k-aligned TLB for NX cards */
+ trident->tlb.entries = NULL;
+ if (trident->device == TRIDENT_DEVICE_ID_NX) {
+ err = snd_trident_tlb_alloc(trident);
+ if (err < 0)
+ 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)
+ return err;
+
+ err = snd_trident_mixer(trident, pcm_spdif_device);
+ if (err < 0)
+ return err;
+
+ /* initialise synth voices */
+ for (i = 0; i < 64; i++) {
+ voice = &trident->synth.voices[i];
+ voice->number = i;
+ voice->trident = trident;
+ }
+ /* initialize pcm mixer entries */
+ for (i = 0; i < 32; i++) {
+ tmix = &trident->pcm_mixer[i];
+ tmix->vol = T4D_DEFAULT_PCM_VOL;
+ tmix->pan = T4D_DEFAULT_PCM_PAN;
+ tmix->rvol = T4D_DEFAULT_PCM_RVOL;
+ tmix->cvol = T4D_DEFAULT_PCM_CVOL;
+ }
+
+ snd_trident_enable_eso(trident);
+
+ snd_trident_proc_init(trident);
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_free
+
+ Description: This routine will free the device specific class for
+ the 4DWave card.
+
+ Parameters: card - card to release
+
+ Returns: None.
+
+ ---------------------------------------------------------------------------*/
+
+static void snd_trident_free(struct snd_card *card)
+{
+ struct snd_trident *trident = card->private_data;
+
+ 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) {
+ outl(0, TRID_REG(trident, NX_TLBC));
+ snd_util_memhdr_free(trident->tlb.memhdr);
+ }
+}
+
+/*---------------------------------------------------------------------------
+ snd_trident_interrupt
+
+ Description: ISR for Trident 4DWave device
+
+ Parameters: trident - device specific private data for 4DWave card
+
+ Problems: It seems that Trident chips generates interrupts more than
+ one time in special cases. The spurious interrupts are
+ detected via sample timer (T4D_STIMER) and computing
+ corresponding delta value. The limits are detected with
+ the method try & fail so it is possible that it won't
+ work on all computers. [jaroslav]
+
+ Returns: None.
+
+ ---------------------------------------------------------------------------*/
+
+static irqreturn_t snd_trident_interrupt(int irq, void *dev_id)
+{
+ struct snd_trident *trident = dev_id;
+ unsigned int audio_int, chn_int, stimer, channel, mask, tmp;
+ int delta;
+ struct snd_trident_voice *voice;
+
+ audio_int = inl(TRID_REG(trident, T4D_MISCINT));
+ if ((audio_int & (ADDRESS_IRQ|MPU401_IRQ)) == 0)
+ return IRQ_NONE;
+ if (audio_int & ADDRESS_IRQ) {
+ // get interrupt status for all channels
+ spin_lock(&trident->reg_lock);
+ stimer = inl(TRID_REG(trident, T4D_STIMER)) & 0x00ffffff;
+ chn_int = inl(TRID_REG(trident, T4D_AINT_A));
+ if (chn_int == 0)
+ goto __skip1;
+ outl(chn_int, TRID_REG(trident, T4D_AINT_A)); /* ack */
+ __skip1:
+ chn_int = inl(TRID_REG(trident, T4D_AINT_B));
+ if (chn_int == 0)
+ goto __skip2;
+ for (channel = 63; channel >= 32; channel--) {
+ mask = 1 << (channel&0x1f);
+ if ((chn_int & mask) == 0)
+ continue;
+ voice = &trident->synth.voices[channel];
+ if (!voice->pcm || voice->substream == NULL) {
+ outl(mask, TRID_REG(trident, T4D_STOP_B));
+ continue;
+ }
+ delta = (int)stimer - (int)voice->stimer;
+ if (delta < 0)
+ delta = -delta;
+ if ((unsigned int)delta < voice->spurious_threshold) {
+ /* do some statistics here */
+ trident->spurious_irq_count++;
+ if (trident->spurious_irq_max_delta < (unsigned int)delta)
+ trident->spurious_irq_max_delta = delta;
+ continue;
+ }
+ voice->stimer = stimer;
+ if (voice->isync) {
+ if (!voice->isync3) {
+ tmp = inw(TRID_REG(trident, T4D_SBBL_SBCL));
+ if (trident->bDMAStart & 0x40)
+ tmp >>= 1;
+ if (tmp > 0)
+ tmp = voice->isync_max - tmp;
+ } else {
+ tmp = inl(TRID_REG(trident, NX_SPCTRL_SPCSO)) & 0x00ffffff;
+ }
+ if (tmp < voice->isync_mark) {
+ if (tmp > 0x10)
+ tmp = voice->isync_ESO - 7;
+ else
+ tmp = voice->isync_ESO + 2;
+ /* update ESO for IRQ voice to preserve sync */
+ snd_trident_stop_voice(trident, voice->number);
+ snd_trident_write_eso_reg(trident, voice, tmp);
+ snd_trident_start_voice(trident, voice->number);
+ }
+ } else if (voice->isync2) {
+ voice->isync2 = 0;
+ /* write original ESO and update CSO for IRQ voice to preserve sync */
+ snd_trident_stop_voice(trident, voice->number);
+ snd_trident_write_cso_reg(trident, voice, voice->isync_mark);
+ snd_trident_write_eso_reg(trident, voice, voice->ESO);
+ snd_trident_start_voice(trident, voice->number);
+ }
+#if 0
+ if (voice->extra) {
+ /* update CSO for extra voice to preserve sync */
+ snd_trident_stop_voice(trident, voice->extra->number);
+ snd_trident_write_cso_reg(trident, voice->extra, 0);
+ snd_trident_start_voice(trident, voice->extra->number);
+ }
+#endif
+ spin_unlock(&trident->reg_lock);
+ snd_pcm_period_elapsed(voice->substream);
+ spin_lock(&trident->reg_lock);
+ }
+ outl(chn_int, TRID_REG(trident, T4D_AINT_B)); /* ack */
+ __skip2:
+ spin_unlock(&trident->reg_lock);
+ }
+ if (audio_int & MPU401_IRQ) {
+ if (trident->rmidi) {
+ snd_mpu401_uart_interrupt(irq, trident->rmidi->private_data);
+ } else {
+ inb(TRID_REG(trident, T4D_MPUR0));
+ }
+ }
+ // outl((ST_TARGET_REACHED | MIXER_OVERFLOW | MIXER_UNDERFLOW), TRID_REG(trident, T4D_MISCINT));
+ return IRQ_HANDLED;
+}
+
+struct snd_trident_voice *snd_trident_alloc_voice(struct snd_trident * trident, int type, int client, int port)
+{
+ struct snd_trident_voice *pvoice;
+ unsigned long flags;
+ int idx;
+
+ spin_lock_irqsave(&trident->voice_alloc, flags);
+ if (type == SNDRV_TRIDENT_VOICE_TYPE_PCM) {
+ idx = snd_trident_allocate_pcm_channel(trident);
+ if(idx < 0) {
+ spin_unlock_irqrestore(&trident->voice_alloc, flags);
+ return NULL;
+ }
+ pvoice = &trident->synth.voices[idx];
+ pvoice->use = 1;
+ pvoice->pcm = 1;
+ pvoice->capture = 0;
+ pvoice->spdif = 0;
+ pvoice->memblk = NULL;
+ pvoice->substream = NULL;
+ spin_unlock_irqrestore(&trident->voice_alloc, flags);
+ return pvoice;
+ }
+ if (type == SNDRV_TRIDENT_VOICE_TYPE_SYNTH) {
+ idx = snd_trident_allocate_synth_channel(trident);
+ if(idx < 0) {
+ spin_unlock_irqrestore(&trident->voice_alloc, flags);
+ return NULL;
+ }
+ pvoice = &trident->synth.voices[idx];
+ pvoice->use = 1;
+ pvoice->synth = 1;
+ pvoice->client = client;
+ pvoice->port = port;
+ pvoice->memblk = NULL;
+ spin_unlock_irqrestore(&trident->voice_alloc, flags);
+ return pvoice;
+ }
+ if (type == SNDRV_TRIDENT_VOICE_TYPE_MIDI) {
+ }
+ spin_unlock_irqrestore(&trident->voice_alloc, flags);
+ return NULL;
+}
+
+EXPORT_SYMBOL(snd_trident_alloc_voice);
+
+void snd_trident_free_voice(struct snd_trident * trident, struct snd_trident_voice *voice)
+{
+ unsigned long flags;
+ void (*private_free)(struct snd_trident_voice *);
+
+ 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;
+ voice->private_free = NULL;
+ voice->private_data = NULL;
+ if (voice->pcm)
+ snd_trident_free_pcm_channel(trident, voice->number);
+ if (voice->synth)
+ snd_trident_free_synth_channel(trident, voice->number);
+ voice->use = voice->pcm = voice->synth = voice->midi = 0;
+ voice->capture = voice->spdif = 0;
+ voice->sample_ops = NULL;
+ voice->substream = NULL;
+ voice->extra = NULL;
+ spin_unlock_irqrestore(&trident->voice_alloc, flags);
+ if (private_free)
+ private_free(voice);
+}
+
+EXPORT_SYMBOL(snd_trident_free_voice);
+
+static void snd_trident_clear_voices(struct snd_trident * trident, unsigned short v_min, unsigned short v_max)
+{
+ unsigned int i, val, mask[2] = { 0, 0 };
+
+ if (snd_BUG_ON(v_min > 63 || 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_SLEEP
+static int snd_trident_suspend(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_trident *trident = card->private_data;
+
+ trident->in_suspend = 1;
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ snd_ac97_suspend(trident->ac97);
+ snd_ac97_suspend(trident->ac97_sec);
+ return 0;
+}
+
+static int snd_trident_resume(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_trident *trident = card->private_data;
+
+ switch (trident->device) {
+ case TRIDENT_DEVICE_ID_DX:
+ snd_trident_4d_dx_init(trident);
+ break;
+ case TRIDENT_DEVICE_ID_NX:
+ snd_trident_4d_nx_init(trident);
+ break;
+ case TRIDENT_DEVICE_ID_SI7018:
+ snd_trident_sis_init(trident);
+ break;
+ }
+
+ snd_ac97_resume(trident->ac97);
+ snd_ac97_resume(trident->ac97_sec);
+
+ /* restore some registers */
+ outl(trident->musicvol_wavevol, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
+
+ snd_trident_enable_eso(trident);
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ trident->in_suspend = 0;
+ return 0;
+}
+
+SIMPLE_DEV_PM_OPS(snd_trident_pm, snd_trident_suspend, snd_trident_resume);
+#endif /* CONFIG_PM_SLEEP */
diff --git a/sound/pci/trident/trident_memory.c b/sound/pci/trident/trident_memory.c
new file mode 100644
index 000000000..05de2b9f4
--- /dev/null
+++ b/sound/pci/trident/trident_memory.c
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@perex.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.
+ */
+
+#include <linux/io.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include "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,addr) \
+ (trident)->tlb.entries[page] = cpu_to_le32((addr) & ~(SNDRV_TRIDENT_PAGE_SIZE-1))
+#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,addr) __set_tlb_bus(trident,page,addr)
+/* fill TLB entrie(s) corresponding to page with silence pointer */
+#define set_silent_tlb(trident,page) __set_tlb_bus(trident, page, 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 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_addr(trident,page) __tlb_to_addr(trident, (page) << 1)
+
+/* fill TLB entries -- we need to fill two entries */
+static inline void set_tlb_bus(struct snd_trident *trident, int page,
+ dma_addr_t addr)
+{
+ page <<= 1;
+ __set_tlb_bus(trident, page, addr);
+ __set_tlb_bus(trident, page+1, addr + SNDRV_TRIDENT_PAGE_SIZE);
+}
+static inline void set_silent_tlb(struct snd_trident *trident, int page)
+{
+ page <<= 1;
+ __set_tlb_bus(trident, page, trident->tlb.silent_page->addr);
+ __set_tlb_bus(trident, page+1, 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_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(struct snd_trident *trident, int page,
+ dma_addr_t addr)
+{
+ int i;
+ page *= UNIT_PAGES;
+ for (i = 0; i < UNIT_PAGES; i++, page++) {
+ __set_tlb_bus(trident, page, addr);
+ addr += SNDRV_TRIDENT_PAGE_SIZE;
+ }
+}
+static inline void set_silent_tlb(struct snd_trident *trident, int page)
+{
+ int i;
+ page *= UNIT_PAGES;
+ for (i = 0; i < UNIT_PAGES; i++, page++)
+ __set_tlb_bus(trident, page, trident->tlb.silent_page->addr);
+}
+
+#endif /* PAGE_SIZE */
+
+/* first and last (aligned) pages of memory block */
+#define firstpg(blk) (((struct snd_trident_memblk_arg *)snd_util_memblk_argptr(blk))->first_page)
+#define lastpg(blk) (((struct snd_trident_memblk_arg *)snd_util_memblk_argptr(blk))->last_page)
+
+/*
+ * search empty pages which may contain given size
+ */
+static struct snd_util_memblk *
+search_empty(struct snd_util_memhdr *hdr, int size)
+{
+ struct snd_util_memblk *blk;
+ int page, psize;
+ struct list_head *p;
+
+ psize = get_aligned_page(size + ALIGN_PAGE_SIZE -1);
+ page = 0;
+ list_for_each(p, &hdr->block) {
+ blk = list_entry(p, struct snd_util_memblk, 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(KERN_ERR "max memory size is 1GB!!\n");
+ return 0;
+ }
+ if (ptr & (SNDRV_TRIDENT_PAGE_SIZE-1)) {
+ snd_printk(KERN_ERR "page is not aligned\n");
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * page allocation for DMA (Scatter-Gather version)
+ */
+static struct snd_util_memblk *
+snd_trident_alloc_sg_pages(struct snd_trident *trident,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_util_memhdr *hdr;
+ struct snd_util_memblk *blk;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int idx, page;
+
+ if (snd_BUG_ON(runtime->dma_bytes <= 0 ||
+ runtime->dma_bytes > SNDRV_TRIDENT_MAX_PAGES *
+ SNDRV_TRIDENT_PAGE_SIZE))
+ return NULL;
+ hdr = trident->tlb.memhdr;
+ if (snd_BUG_ON(!hdr))
+ return NULL;
+
+
+
+ mutex_lock(&hdr->block_mutex);
+ blk = search_empty(hdr, runtime->dma_bytes);
+ if (blk == NULL) {
+ mutex_unlock(&hdr->block_mutex);
+ return NULL;
+ }
+
+ /* set TLB entries */
+ idx = 0;
+ for (page = firstpg(blk); page <= lastpg(blk); page++, idx++) {
+ unsigned long ofs = idx << PAGE_SHIFT;
+ dma_addr_t addr = snd_pcm_sgbuf_get_addr(substream, ofs);
+ if (! is_valid_page(addr)) {
+ __snd_util_mem_free(hdr, blk);
+ mutex_unlock(&hdr->block_mutex);
+ return NULL;
+ }
+ set_tlb_bus(trident, page, addr);
+ }
+ mutex_unlock(&hdr->block_mutex);
+ return blk;
+}
+
+/*
+ * page allocation for DMA (contiguous version)
+ */
+static struct snd_util_memblk *
+snd_trident_alloc_cont_pages(struct snd_trident *trident,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_util_memhdr *hdr;
+ struct snd_util_memblk *blk;
+ int page;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ dma_addr_t addr;
+
+ if (snd_BUG_ON(runtime->dma_bytes <= 0 ||
+ runtime->dma_bytes > SNDRV_TRIDENT_MAX_PAGES *
+ SNDRV_TRIDENT_PAGE_SIZE))
+ return NULL;
+ hdr = trident->tlb.memhdr;
+ if (snd_BUG_ON(!hdr))
+ return NULL;
+
+ mutex_lock(&hdr->block_mutex);
+ blk = search_empty(hdr, runtime->dma_bytes);
+ if (blk == NULL) {
+ mutex_unlock(&hdr->block_mutex);
+ return NULL;
+ }
+
+ /* set TLB entries */
+ addr = runtime->dma_addr;
+ for (page = firstpg(blk); page <= lastpg(blk); page++,
+ addr += SNDRV_TRIDENT_PAGE_SIZE) {
+ if (! is_valid_page(addr)) {
+ __snd_util_mem_free(hdr, blk);
+ mutex_unlock(&hdr->block_mutex);
+ return NULL;
+ }
+ set_tlb_bus(trident, page, addr);
+ }
+ mutex_unlock(&hdr->block_mutex);
+ return blk;
+}
+
+/*
+ * page allocation for DMA
+ */
+struct snd_util_memblk *
+snd_trident_alloc_pages(struct snd_trident *trident,
+ struct snd_pcm_substream *substream)
+{
+ if (snd_BUG_ON(!trident || !substream))
+ 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(struct snd_trident *trident,
+ struct snd_util_memblk *blk)
+{
+ struct snd_util_memhdr *hdr;
+ int page;
+
+ if (snd_BUG_ON(!trident || !blk))
+ return -EINVAL;
+
+ hdr = trident->tlb.memhdr;
+ mutex_lock(&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);
+ mutex_unlock(&hdr->block_mutex);
+ return 0;
+}