]> pilppa.com Git - linux-2.6-omap-h63xx.git/commitdiff
[ALSA] sis7019: support the SiS 7019 Audio Accelerator
authorDavid Dillow <dave@thedillows.org>
Fri, 14 Dec 2007 13:40:23 +0000 (14:40 +0100)
committerJaroslav Kysela <perex@perex.cz>
Thu, 31 Jan 2008 16:29:36 +0000 (17:29 +0100)
Basic audio support for the SiS 7019 Audio Accelerator as found in the
SiS 55x SoC. There is currently no synth support at the moment, but
audio playback and capture with two periods per buffer has seen
extensive use. Arbitrary period and buffer sizes (with multiple periods
per buffer) have seen light testing, but are believed to be production
ready.

Signed-off-by: David Dillow <dave@thedillows.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
sound/pci/Kconfig
sound/pci/Makefile
sound/pci/sis7019.c [new file with mode: 0644]
sound/pci/sis7019.h [new file with mode: 0644]

index 356bf21a15060219077a255bf4b8b0afd81e29c7..45f0f6c2f353a09abbb1346210331701d16ce3eb 100644 (file)
@@ -802,6 +802,16 @@ config SND_RME9652
          To compile this driver as a module, choose M here: the module
          will be called snd-rme9652.
 
+config SND_SIS7019
+       tristate "SiS 7019 Audio Accelerator"
+       depends on SND && X86 && !X86_64
+       select SND_AC97_CODEC
+       help
+         Say Y here to include support for the SiS 7019 Audio Accelerator.
+
+         To compile this driver as a module, choose M here: the module
+         will be called snd-sis7019.
+
 config SND_SONICVIBES
        tristate "S3 SonicVibes"
        depends on SND
index 09ddc82eeca22130b17a4597e583868cad93cbd7..56738da9c14ae770f09155758a22c8f0e2efc63b 100644 (file)
@@ -23,6 +23,7 @@ snd-intel8x0m-objs := intel8x0m.o
 snd-maestro3-objs := maestro3.o
 snd-rme32-objs := rme32.o
 snd-rme96-objs := rme96.o
+snd-sis7019-objs := sis7019.o
 snd-sonicvibes-objs := sonicvibes.o
 snd-via82xx-objs := via82xx.o
 snd-via82xx-modem-objs := via82xx_modem.o
@@ -48,6 +49,7 @@ obj-$(CONFIG_SND_INTEL8X0M) += snd-intel8x0m.o
 obj-$(CONFIG_SND_MAESTRO3) += snd-maestro3.o
 obj-$(CONFIG_SND_RME32) += snd-rme32.o
 obj-$(CONFIG_SND_RME96) += snd-rme96.o
+obj-$(CONFIG_SND_SIS7019) += snd-sis7019.o
 obj-$(CONFIG_SND_SONICVIBES) += snd-sonicvibes.o
 obj-$(CONFIG_SND_VIA82XX) += snd-via82xx.o
 obj-$(CONFIG_SND_VIA82XX_MODEM) += snd-via82xx-modem.o
diff --git a/sound/pci/sis7019.c b/sound/pci/sis7019.c
new file mode 100644 (file)
index 0000000..2f17859
--- /dev/null
@@ -0,0 +1,1461 @@
+/*
+ *  Driver for SiS7019 Audio Accelerator
+ *
+ *  Copyright (C) 2004-2007, David Dillow
+ *  Written by David Dillow <dave@thedillows.org>
+ *  Inspired by the Trident 4D-WaveDX/NX driver.
+ *
+ *  All rights reserved.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, version 2.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include "sis7019.h"
+
+MODULE_AUTHOR("David Dillow <dave@thedillows.org>");
+MODULE_DESCRIPTION("SiS7019");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{SiS,SiS7019 Audio Accelerator}}");
+
+static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;  /* ID for this card */
+static int enable = 1;
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for SiS7019 Audio Accelerator.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for SiS7019 Audio Accelerator.");
+module_param(enable, bool, 0444);
+MODULE_PARM_DESC(enable, "Enable SiS7019 Audio Accelerator.");
+
+static struct pci_device_id snd_sis7019_ids[] = {
+       { PCI_DEVICE(PCI_VENDOR_ID_SI, 0x7019) },
+       { 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_sis7019_ids);
+
+/* There are three timing modes for the voices.
+ *
+ * For both playback and capture, when the buffer is one or two periods long,
+ * we use the hardware's built-in Mid-Loop Interrupt and End-Loop Interrupt
+ * to let us know when the periods have ended.
+ *
+ * When performing playback with more than two periods per buffer, we set
+ * the "Stop Sample Offset" and tell the hardware to interrupt us when we
+ * reach it. We then update the offset and continue on until we are
+ * interrupted for the next period.
+ *
+ * Capture channels do not have a SSO, so we allocate a playback channel to
+ * use as a timer for the capture periods. We use the SSO on the playback
+ * channel to clock out virtual periods, and adjust the virtual period length
+ * to maintain synchronization. This algorithm came from the Trident driver.
+ *
+ * FIXME: It'd be nice to make use of some of the synth features in the
+ * hardware, but a woeful lack of documentation is a significant roadblock.
+ */
+struct voice {
+       u16 flags;
+#define        VOICE_IN_USE            1
+#define        VOICE_CAPTURE           2
+#define        VOICE_SSO_TIMING        4
+#define        VOICE_SYNC_TIMING       8
+       u16 sync_cso;
+       u16 period_size;
+       u16 buffer_size;
+       u16 sync_period_size;
+       u16 sync_buffer_size;
+       u32 sso;
+       u32 vperiod;
+       struct snd_pcm_substream *substream;
+       struct voice *timing;
+       void __iomem *ctrl_base;
+       void __iomem *wave_base;
+       void __iomem *sync_base;
+       int num;
+};
+
+/* We need four pages to store our wave parameters during a suspend. If
+ * we're not doing power management, we still need to allocate a page
+ * for the silence buffer.
+ */
+#ifdef CONFIG_PM
+#define SIS_SUSPEND_PAGES      4
+#else
+#define SIS_SUSPEND_PAGES      1
+#endif
+
+struct sis7019 {
+       unsigned long ioport;
+       void __iomem *ioaddr;
+       int irq;
+       int codecs_present;
+
+       struct pci_dev *pci;
+       struct snd_pcm *pcm;
+       struct snd_card *card;
+       struct snd_ac97 *ac97[3];
+
+       /* Protect against more than one thread hitting the AC97
+        * registers (in a more polite manner than pounding the hardware
+        * semaphore)
+        */
+       struct mutex ac97_mutex;
+
+       /* voice_lock protects allocation/freeing of the voice descriptions
+        */
+       spinlock_t voice_lock;
+
+       struct voice voices[64];
+       struct voice capture_voice;
+
+       /* Allocate pages to store the internal wave state during
+        * suspends. When we're operating, this can be used as a silence
+        * buffer for a timing channel.
+        */
+       void *suspend_state[SIS_SUSPEND_PAGES];
+
+       int silence_users;
+       dma_addr_t silence_dma_addr;
+};
+
+#define SIS_PRIMARY_CODEC_PRESENT      0x0001
+#define SIS_SECONDARY_CODEC_PRESENT    0x0002
+#define SIS_TERTIARY_CODEC_PRESENT     0x0004
+
+/* The HW offset parameters (Loop End, Stop Sample, End Sample) have a
+ * documented range of 8-0xfff8 samples. Given that they are 0-based,
+ * that places our period/buffer range at 9-0xfff9 samples. That makes the
+ * max buffer size 0xfff9 samples * 2 channels * 2 bytes per sample, and
+ * max samples / min samples gives us the max periods in a buffer.
+ *
+ * We'll add a constraint upon open that limits the period and buffer sample
+ * size to values that are legal for the hardware.
+ */
+static struct snd_pcm_hardware sis_playback_hw_info = {
+       .info = (SNDRV_PCM_INFO_MMAP |
+                SNDRV_PCM_INFO_MMAP_VALID |
+                SNDRV_PCM_INFO_INTERLEAVED |
+                SNDRV_PCM_INFO_BLOCK_TRANSFER |
+                SNDRV_PCM_INFO_SYNC_START |
+                SNDRV_PCM_INFO_RESUME),
+       .formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
+                   SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE),
+       .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS,
+       .rate_min = 4000,
+       .rate_max = 48000,
+       .channels_min = 1,
+       .channels_max = 2,
+       .buffer_bytes_max = (0xfff9 * 4),
+       .period_bytes_min = 9,
+       .period_bytes_max = (0xfff9 * 4),
+       .periods_min = 1,
+       .periods_max = (0xfff9 / 9),
+};
+
+static struct snd_pcm_hardware sis_capture_hw_info = {
+       .info = (SNDRV_PCM_INFO_MMAP |
+                SNDRV_PCM_INFO_MMAP_VALID |
+                SNDRV_PCM_INFO_INTERLEAVED |
+                SNDRV_PCM_INFO_BLOCK_TRANSFER |
+                SNDRV_PCM_INFO_SYNC_START |
+                SNDRV_PCM_INFO_RESUME),
+       .formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
+                   SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE),
+       .rates = SNDRV_PCM_RATE_48000,
+       .rate_min = 4000,
+       .rate_max = 48000,
+       .channels_min = 1,
+       .channels_max = 2,
+       .buffer_bytes_max = (0xfff9 * 4),
+       .period_bytes_min = 9,
+       .period_bytes_max = (0xfff9 * 4),
+       .periods_min = 1,
+       .periods_max = (0xfff9 / 9),
+};
+
+static void sis_update_sso(struct voice *voice, u16 period)
+{
+       void __iomem *base = voice->ctrl_base;
+
+       voice->sso += period;
+       if (voice->sso >= voice->buffer_size)
+               voice->sso -= voice->buffer_size;
+
+       /* Enforce the documented hardware minimum offset */
+       if (voice->sso < 8)
+               voice->sso = 8;
+
+       /* The SSO is in the upper 16 bits of the register. */
+       writew(voice->sso & 0xffff, base + SIS_PLAY_DMA_SSO_ESO + 2);
+}
+
+static void sis_update_voice(struct voice *voice)
+{
+       if (voice->flags & VOICE_SSO_TIMING) {
+               sis_update_sso(voice, voice->period_size);
+       } else if (voice->flags & VOICE_SYNC_TIMING) {
+               int sync;
+
+               /* If we've not hit the end of the virtual period, update
+                * our records and keep going.
+                */
+               if (voice->vperiod > voice->period_size) {
+                       voice->vperiod -= voice->period_size;
+                       if (voice->vperiod < voice->period_size)
+                               sis_update_sso(voice, voice->vperiod);
+                       else
+                               sis_update_sso(voice, voice->period_size);
+                       return;
+               }
+
+               /* Calculate our relative offset between the target and
+                * the actual CSO value. Since we're operating in a loop,
+                * if the value is more than half way around, we can
+                * consider ourselves wrapped.
+                */
+               sync = voice->sync_cso;
+               sync -= readw(voice->sync_base + SIS_CAPTURE_DMA_FORMAT_CSO);
+               if (sync > (voice->sync_buffer_size / 2))
+                       sync -= voice->sync_buffer_size;
+
+               /* If sync is positive, then we interrupted too early, and
+                * we'll need to come back in a few samples and try again.
+                * There's a minimum wait, as it takes some time for the DMA
+                * engine to startup, etc...
+                */
+               if (sync > 0) {
+                       if (sync < 16)
+                               sync = 16;
+                       sis_update_sso(voice, sync);
+                       return;
+               }
+
+               /* Ok, we interrupted right on time, or (hopefully) just
+                * a bit late. We'll adjst our next waiting period based
+                * on how close we got.
+                *
+                * We need to stay just behind the actual channel to ensure
+                * it really is past a period when we get our interrupt --
+                * otherwise we'll fall into the early code above and have
+                * a minimum wait time, which makes us quite late here,
+                * eating into the user's time to refresh the buffer, esp.
+                * if using small periods.
+                *
+                * If we're less than 9 samples behind, we're on target.
+                */
+               if (sync > -9)
+                       voice->vperiod = voice->sync_period_size + 1;
+               else
+                       voice->vperiod = voice->sync_period_size - 4;
+
+               if (voice->vperiod < voice->buffer_size) {
+                       sis_update_sso(voice, voice->vperiod);
+                       voice->vperiod = 0;
+               } else
+                       sis_update_sso(voice, voice->period_size);
+
+               sync = voice->sync_cso + voice->sync_period_size;
+               if (sync >= voice->sync_buffer_size)
+                       sync -= voice->sync_buffer_size;
+               voice->sync_cso = sync;
+       }
+
+       snd_pcm_period_elapsed(voice->substream);
+}
+
+static void sis_voice_irq(u32 status, struct voice *voice)
+{
+       int bit;
+
+       while (status) {
+               bit = __ffs(status);
+               status >>= bit + 1;
+               voice += bit;
+               sis_update_voice(voice);
+               voice++;
+       }
+}
+
+static irqreturn_t sis_interrupt(int irq, void *dev)
+{
+       struct sis7019 *sis = dev;
+       unsigned long io = sis->ioport;
+       struct voice *voice;
+       u32 intr, status;
+
+       /* We only use the DMA interrupts, and we don't enable any other
+        * source of interrupts. But, it is possible to see an interupt
+        * status that didn't actually interrupt us, so eliminate anything
+        * we're not expecting to avoid falsely claiming an IRQ, and an
+        * ensuing endless loop.
+        */
+       intr = inl(io + SIS_GISR);
+       intr &= SIS_GISR_AUDIO_PLAY_DMA_IRQ_STATUS |
+               SIS_GISR_AUDIO_RECORD_DMA_IRQ_STATUS;
+       if (!intr)
+               return IRQ_NONE;
+
+       do {
+               status = inl(io + SIS_PISR_A);
+               if (status) {
+                       sis_voice_irq(status, sis->voices);
+                       outl(status, io + SIS_PISR_A);
+               }
+
+               status = inl(io + SIS_PISR_B);
+               if (status) {
+                       sis_voice_irq(status, &sis->voices[32]);
+                       outl(status, io + SIS_PISR_B);
+               }
+
+               status = inl(io + SIS_RISR);
+               if (status) {
+                       voice = &sis->capture_voice;
+                       if (!voice->timing)
+                               snd_pcm_period_elapsed(voice->substream);
+
+                       outl(status, io + SIS_RISR);
+               }
+
+               outl(intr, io + SIS_GISR);
+               intr = inl(io + SIS_GISR);
+               intr &= SIS_GISR_AUDIO_PLAY_DMA_IRQ_STATUS |
+                       SIS_GISR_AUDIO_RECORD_DMA_IRQ_STATUS;
+       } while (intr);
+
+       return IRQ_HANDLED;
+}
+
+static u32 sis_rate_to_delta(unsigned int rate)
+{
+       u32 delta;
+
+       /* This was copied from the trident driver, but it seems its gotten
+        * around a bit... nevertheless, it works well.
+        *
+        * We special case 44100 and 8000 since rounding with the equation
+        * does not give us an accurate enough value. For 11025 and 22050
+        * the equation gives us the best answer. All other frequencies will
+        * also use the equation. JDW
+        */
+       if (rate == 44100)
+               delta = 0xeb3;
+       else if (rate == 8000)
+               delta = 0x2ab;
+       else if (rate == 48000)
+               delta = 0x1000;
+       else
+               delta = (((rate << 12) + 24000) / 48000) & 0x0000ffff;
+       return delta;
+}
+
+static void __sis_map_silence(struct sis7019 *sis)
+{
+       /* Helper function: must hold sis->voice_lock on entry */
+       if (!sis->silence_users)
+               sis->silence_dma_addr = pci_map_single(sis->pci,
+                                               sis->suspend_state[0],
+                                               4096, PCI_DMA_TODEVICE);
+       sis->silence_users++;
+}
+
+static void __sis_unmap_silence(struct sis7019 *sis)
+{
+       /* Helper function: must hold sis->voice_lock on entry */
+       sis->silence_users--;
+       if (!sis->silence_users)
+               pci_unmap_single(sis->pci, sis->silence_dma_addr, 4096,
+                                       PCI_DMA_TODEVICE);
+}
+
+static void sis_free_voice(struct sis7019 *sis, struct voice *voice)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&sis->voice_lock, flags);
+       if (voice->timing) {
+               __sis_unmap_silence(sis);
+               voice->timing->flags &= ~(VOICE_IN_USE | VOICE_SSO_TIMING |
+                                               VOICE_SYNC_TIMING);
+               voice->timing = NULL;
+       }
+       voice->flags &= ~(VOICE_IN_USE | VOICE_SSO_TIMING | VOICE_SYNC_TIMING);
+       spin_unlock_irqrestore(&sis->voice_lock, flags);
+}
+
+static struct voice *__sis_alloc_playback_voice(struct sis7019 *sis)
+{
+       /* Must hold the voice_lock on entry */
+       struct voice *voice;
+       int i;
+
+       for (i = 0; i < 64; i++) {
+               voice = &sis->voices[i];
+               if (voice->flags & VOICE_IN_USE)
+                       continue;
+               voice->flags |= VOICE_IN_USE;
+               goto found_one;
+       }
+       voice = NULL;
+
+found_one:
+       return voice;
+}
+
+static struct voice *sis_alloc_playback_voice(struct sis7019 *sis)
+{
+       struct voice *voice;
+       unsigned long flags;
+
+       spin_lock_irqsave(&sis->voice_lock, flags);
+       voice = __sis_alloc_playback_voice(sis);
+       spin_unlock_irqrestore(&sis->voice_lock, flags);
+
+       return voice;
+}
+
+static int sis_alloc_timing_voice(struct snd_pcm_substream *substream,
+                                       struct snd_pcm_hw_params *hw_params)
+{
+       struct sis7019 *sis = snd_pcm_substream_chip(substream);
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct voice *voice = runtime->private_data;
+       unsigned int period_size, buffer_size;
+       unsigned long flags;
+       int needed;
+
+       /* If there are one or two periods per buffer, we don't need a
+        * timing voice, as we can use the capture channel's interrupts
+        * to clock out the periods.
+        */
+       period_size = params_period_size(hw_params);
+       buffer_size = params_buffer_size(hw_params);
+       needed = (period_size != buffer_size &&
+                       period_size != (buffer_size / 2));
+
+       if (needed && !voice->timing) {
+               spin_lock_irqsave(&sis->voice_lock, flags);
+               voice->timing = __sis_alloc_playback_voice(sis);
+               if (voice->timing)
+                       __sis_map_silence(sis);
+               spin_unlock_irqrestore(&sis->voice_lock, flags);
+               if (!voice->timing)
+                       return -ENOMEM;
+               voice->timing->substream = substream;
+       } else if (!needed && voice->timing) {
+               sis_free_voice(sis, voice);
+               voice->timing = NULL;
+       }
+
+       return 0;
+}
+
+static int sis_playback_open(struct snd_pcm_substream *substream)
+{
+       struct sis7019 *sis = snd_pcm_substream_chip(substream);
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct voice *voice;
+
+       voice = sis_alloc_playback_voice(sis);
+       if (!voice)
+               return -EAGAIN;
+
+       voice->substream = substream;
+       runtime->private_data = voice;
+       runtime->hw = sis_playback_hw_info;
+       snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+                                               9, 0xfff9);
+       snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+                                               9, 0xfff9);
+       snd_pcm_set_sync(substream);
+       return 0;
+}
+
+static int sis_substream_close(struct snd_pcm_substream *substream)
+{
+       struct sis7019 *sis = snd_pcm_substream_chip(substream);
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct voice *voice = runtime->private_data;
+
+       sis_free_voice(sis, voice);
+       return 0;
+}
+
+static int sis_playback_hw_params(struct snd_pcm_substream *substream,
+                                       struct snd_pcm_hw_params *hw_params)
+{
+       return snd_pcm_lib_malloc_pages(substream,
+                                       params_buffer_bytes(hw_params));
+}
+
+static int sis_hw_free(struct snd_pcm_substream *substream)
+{
+       return snd_pcm_lib_free_pages(substream);
+}
+
+static int sis_pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct voice *voice = runtime->private_data;
+       void __iomem *ctrl_base = voice->ctrl_base;
+       void __iomem *wave_base = voice->wave_base;
+       u32 format, dma_addr, control, sso_eso, delta, reg;
+       u16 leo;
+
+       /* We rely on the PCM core to ensure that the parameters for this
+        * substream do not change on us while we're programming the HW.
+        */
+       format = 0;
+       if (snd_pcm_format_width(runtime->format) == 8)
+               format |= SIS_PLAY_DMA_FORMAT_8BIT;
+       if (!snd_pcm_format_signed(runtime->format))
+               format |= SIS_PLAY_DMA_FORMAT_UNSIGNED;
+       if (runtime->channels == 1)
+               format |= SIS_PLAY_DMA_FORMAT_MONO;
+
+       /* The baseline setup is for a single period per buffer, and
+        * we add bells and whistles as needed from there.
+        */
+       dma_addr = runtime->dma_addr;
+       leo = runtime->buffer_size - 1;
+       control = leo | SIS_PLAY_DMA_LOOP | SIS_PLAY_DMA_INTR_AT_LEO;
+       sso_eso = leo;
+
+       if (runtime->period_size == (runtime->buffer_size / 2)) {
+               control |= SIS_PLAY_DMA_INTR_AT_MLP;
+       } else if (runtime->period_size != runtime->buffer_size) {
+               voice->flags |= VOICE_SSO_TIMING;
+               voice->sso = runtime->period_size - 1;
+               voice->period_size = runtime->period_size;
+               voice->buffer_size = runtime->buffer_size;
+
+               control &= ~SIS_PLAY_DMA_INTR_AT_LEO;
+               control |= SIS_PLAY_DMA_INTR_AT_SSO;
+               sso_eso |= (runtime->period_size - 1) << 16;
+       }
+
+       delta = sis_rate_to_delta(runtime->rate);
+
+       /* Ok, we're ready to go, set up the channel.
+        */
+       writel(format, ctrl_base + SIS_PLAY_DMA_FORMAT_CSO);
+       writel(dma_addr, ctrl_base + SIS_PLAY_DMA_BASE);
+       writel(control, ctrl_base + SIS_PLAY_DMA_CONTROL);
+       writel(sso_eso, ctrl_base + SIS_PLAY_DMA_SSO_ESO);
+
+       for (reg = 0; reg < SIS_WAVE_SIZE; reg += 4)
+               writel(0, wave_base + reg);
+
+       writel(SIS_WAVE_GENERAL_WAVE_VOLUME, wave_base + SIS_WAVE_GENERAL);
+       writel(delta << 16, wave_base + SIS_WAVE_GENERAL_ARTICULATION);
+       writel(SIS_WAVE_CHANNEL_CONTROL_FIRST_SAMPLE |
+                       SIS_WAVE_CHANNEL_CONTROL_AMP_ENABLE |
+                       SIS_WAVE_CHANNEL_CONTROL_INTERPOLATE_ENABLE,
+                       wave_base + SIS_WAVE_CHANNEL_CONTROL);
+
+       /* Force PCI writes to post. */
+       readl(ctrl_base);
+
+       return 0;
+}
+
+static int sis_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+       struct sis7019 *sis = snd_pcm_substream_chip(substream);
+       unsigned long io = sis->ioport;
+       struct snd_pcm_substream *s;
+       struct voice *voice;
+       void *chip;
+       int starting;
+       u32 record = 0;
+       u32 play[2] = { 0, 0 };
+
+       /* No locks needed, as the PCM core will hold the locks on the
+        * substreams, and the HW will only start/stop the indicated voices
+        * without changing the state of the others.
+        */
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+       case SNDRV_PCM_TRIGGER_RESUME:
+               starting = 1;
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+               starting = 0;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_pcm_group_for_each_entry(s, substream) {
+               /* Make sure it is for us... */
+               chip = snd_pcm_substream_chip(s);
+               if (chip != sis)
+                       continue;
+
+               voice = s->runtime->private_data;
+               if (voice->flags & VOICE_CAPTURE) {
+                       record |= 1 << voice->num;
+                       voice = voice->timing;
+               }
+
+               /* voice could be NULL if this a recording stream, and it
+                * doesn't have an external timing channel.
+                */
+               if (voice)
+                       play[voice->num / 32] |= 1 << (voice->num & 0x1f);
+
+               snd_pcm_trigger_done(s, substream);
+       }
+
+       if (starting) {
+               if (record)
+                       outl(record, io + SIS_RECORD_START_REG);
+               if (play[0])
+                       outl(play[0], io + SIS_PLAY_START_A_REG);
+               if (play[1])
+                       outl(play[1], io + SIS_PLAY_START_B_REG);
+       } else {
+               if (record)
+                       outl(record, io + SIS_RECORD_STOP_REG);
+               if (play[0])
+                       outl(play[0], io + SIS_PLAY_STOP_A_REG);
+               if (play[1])
+                       outl(play[1], io + SIS_PLAY_STOP_B_REG);
+       }
+       return 0;
+}
+
+static snd_pcm_uframes_t sis_pcm_pointer(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct voice *voice = runtime->private_data;
+       u32 cso;
+
+       cso = readl(voice->ctrl_base + SIS_PLAY_DMA_FORMAT_CSO);
+       cso &= 0xffff;
+       return cso;
+}
+
+static int sis_capture_open(struct snd_pcm_substream *substream)
+{
+       struct sis7019 *sis = snd_pcm_substream_chip(substream);
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct voice *voice = &sis->capture_voice;
+       unsigned long flags;
+
+       /* FIXME: The driver only supports recording from one channel
+        * at the moment, but it could support more.
+        */
+       spin_lock_irqsave(&sis->voice_lock, flags);
+       if (voice->flags & VOICE_IN_USE)
+               voice = NULL;
+       else
+               voice->flags |= VOICE_IN_USE;
+       spin_unlock_irqrestore(&sis->voice_lock, flags);
+
+       if (!voice)
+               return -EAGAIN;
+
+       voice->substream = substream;
+       runtime->private_data = voice;
+       runtime->hw = sis_capture_hw_info;
+       runtime->hw.rates = sis->ac97[0]->rates[AC97_RATES_ADC];
+       snd_pcm_limit_hw_rates(runtime);
+       snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+                                               9, 0xfff9);
+       snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+                                               9, 0xfff9);
+       snd_pcm_set_sync(substream);
+       return 0;
+}
+
+static int sis_capture_hw_params(struct snd_pcm_substream *substream,
+                                       struct snd_pcm_hw_params *hw_params)
+{
+       struct sis7019 *sis = snd_pcm_substream_chip(substream);
+       int rc;
+
+       rc = snd_ac97_set_rate(sis->ac97[0], AC97_PCM_LR_ADC_RATE,
+                                               params_rate(hw_params));
+       if (rc)
+               goto out;
+
+       rc = snd_pcm_lib_malloc_pages(substream,
+                                       params_buffer_bytes(hw_params));
+       if (rc < 0)
+               goto out;
+
+       rc = sis_alloc_timing_voice(substream, hw_params);
+
+out:
+       return rc;
+}
+
+static void sis_prepare_timing_voice(struct voice *voice,
+                                       struct snd_pcm_substream *substream)
+{
+       struct sis7019 *sis = snd_pcm_substream_chip(substream);
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct voice *timing = voice->timing;
+       void __iomem *play_base = timing->ctrl_base;
+       void __iomem *wave_base = timing->wave_base;
+       u16 buffer_size, period_size;
+       u32 format, control, sso_eso, delta;
+       u32 vperiod, sso, reg;
+
+       /* Set our initial buffer and period as large as we can given a
+        * single page of silence.
+        */
+       buffer_size = 4096 / runtime->channels;
+       buffer_size /= snd_pcm_format_size(runtime->format, 1);
+       period_size = buffer_size;
+
+       /* Initially, we want to interrupt just a bit behind the end of
+        * the period we're clocking out. 10 samples seems to give a good
+        * delay.
+        *
+        * We want to spread our interrupts throughout the virtual period,
+        * so that we don't end up with two interrupts back to back at the
+        * end -- this helps minimize the effects of any jitter. Adjust our
+        * clocking period size so that the last period is at least a fourth
+        * of a full period.
+        *
+        * This is all moot if we don't need to use virtual periods.
+        */
+       vperiod = runtime->period_size + 10;
+       if (vperiod > period_size) {
+               u16 tail = vperiod % period_size;
+               u16 quarter_period = period_size / 4;
+
+               if (tail && tail < quarter_period) {
+                       u16 loops = vperiod / period_size;
+
+                       tail = quarter_period - tail;
+                       tail += loops - 1;
+                       tail /= loops;
+                       period_size -= tail;
+               }
+
+               sso = period_size - 1;
+       } else {
+               /* The initial period will fit inside the buffer, so we
+                * don't need to use virtual periods -- disable them.
+                */
+               period_size = runtime->period_size;
+               sso = vperiod - 1;
+               vperiod = 0;
+       }
+
+       /* The interrupt handler implements the timing syncronization, so
+        * setup its state.
+        */
+       timing->flags |= VOICE_SYNC_TIMING;
+       timing->sync_base = voice->ctrl_base;
+       timing->sync_cso = runtime->period_size - 1;
+       timing->sync_period_size = runtime->period_size;
+       timing->sync_buffer_size = runtime->buffer_size;
+       timing->period_size = period_size;
+       timing->buffer_size = buffer_size;
+       timing->sso = sso;
+       timing->vperiod = vperiod;
+
+       /* Using unsigned samples with the all-zero silence buffer
+        * forces the output to the lower rail, killing playback.
+        * So ignore unsigned vs signed -- it doesn't change the timing.
+        */
+       format = 0;
+       if (snd_pcm_format_width(runtime->format) == 8)
+               format = SIS_CAPTURE_DMA_FORMAT_8BIT;
+       if (runtime->channels == 1)
+               format |= SIS_CAPTURE_DMA_FORMAT_MONO;
+
+       control = timing->buffer_size - 1;
+       control |= SIS_PLAY_DMA_LOOP | SIS_PLAY_DMA_INTR_AT_SSO;
+       sso_eso = timing->buffer_size - 1;
+       sso_eso |= timing->sso << 16;
+
+       delta = sis_rate_to_delta(runtime->rate);
+
+       /* We've done the math, now configure the channel.
+        */
+       writel(format, play_base + SIS_PLAY_DMA_FORMAT_CSO);
+       writel(sis->silence_dma_addr, play_base + SIS_PLAY_DMA_BASE);
+       writel(control, play_base + SIS_PLAY_DMA_CONTROL);
+       writel(sso_eso, play_base + SIS_PLAY_DMA_SSO_ESO);
+
+       for (reg = 0; reg < SIS_WAVE_SIZE; reg += 4)
+               writel(0, wave_base + reg);
+
+       writel(SIS_WAVE_GENERAL_WAVE_VOLUME, wave_base + SIS_WAVE_GENERAL);
+       writel(delta << 16, wave_base + SIS_WAVE_GENERAL_ARTICULATION);
+       writel(SIS_WAVE_CHANNEL_CONTROL_FIRST_SAMPLE |
+                       SIS_WAVE_CHANNEL_CONTROL_AMP_ENABLE |
+                       SIS_WAVE_CHANNEL_CONTROL_INTERPOLATE_ENABLE,
+                       wave_base + SIS_WAVE_CHANNEL_CONTROL);
+}
+
+static int sis_pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct voice *voice = runtime->private_data;
+       void __iomem *rec_base = voice->ctrl_base;
+       u32 format, dma_addr, control;
+       u16 leo;
+
+       /* We rely on the PCM core to ensure that the parameters for this
+        * substream do not change on us while we're programming the HW.
+        */
+       format = 0;
+       if (snd_pcm_format_width(runtime->format) == 8)
+               format = SIS_CAPTURE_DMA_FORMAT_8BIT;
+       if (!snd_pcm_format_signed(runtime->format))
+               format |= SIS_CAPTURE_DMA_FORMAT_UNSIGNED;
+       if (runtime->channels == 1)
+               format |= SIS_CAPTURE_DMA_FORMAT_MONO;
+
+       dma_addr = runtime->dma_addr;
+       leo = runtime->buffer_size - 1;
+       control = leo | SIS_CAPTURE_DMA_LOOP;
+
+       /* If we've got more than two periods per buffer, then we have
+        * use a timing voice to clock out the periods. Otherwise, we can
+        * use the capture channel's interrupts.
+        */
+       if (voice->timing) {
+               sis_prepare_timing_voice(voice, substream);
+       } else {
+               control |= SIS_CAPTURE_DMA_INTR_AT_LEO;
+               if (runtime->period_size != runtime->buffer_size)
+                       control |= SIS_CAPTURE_DMA_INTR_AT_MLP;
+       }
+
+       writel(format, rec_base + SIS_CAPTURE_DMA_FORMAT_CSO);
+       writel(dma_addr, rec_base + SIS_CAPTURE_DMA_BASE);
+       writel(control, rec_base + SIS_CAPTURE_DMA_CONTROL);
+
+       /* Force the writes to post. */
+       readl(rec_base);
+
+       return 0;
+}
+
+static struct snd_pcm_ops sis_playback_ops = {
+       .open = sis_playback_open,
+       .close = sis_substream_close,
+       .ioctl = snd_pcm_lib_ioctl,
+       .hw_params = sis_playback_hw_params,
+       .hw_free = sis_hw_free,
+       .prepare = sis_pcm_playback_prepare,
+       .trigger = sis_pcm_trigger,
+       .pointer = sis_pcm_pointer,
+};
+
+static struct snd_pcm_ops sis_capture_ops = {
+       .open = sis_capture_open,
+       .close = sis_substream_close,
+       .ioctl = snd_pcm_lib_ioctl,
+       .hw_params = sis_capture_hw_params,
+       .hw_free = sis_hw_free,
+       .prepare = sis_pcm_capture_prepare,
+       .trigger = sis_pcm_trigger,
+       .pointer = sis_pcm_pointer,
+};
+
+static int __devinit sis_pcm_create(struct sis7019 *sis)
+{
+       struct snd_pcm *pcm;
+       int rc;
+
+       /* We have 64 voices, and the driver currently records from
+        * only one channel, though that could change in the future.
+        */
+       rc = snd_pcm_new(sis->card, "SiS7019", 0, 64, 1, &pcm);
+       if (rc)
+               return rc;
+
+       pcm->private_data = sis;
+       strcpy(pcm->name, "SiS7019");
+       sis->pcm = pcm;
+
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &sis_playback_ops);
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &sis_capture_ops);
+
+       /* Try to preallocate some memory, but it's not the end of the
+        * world if this fails.
+        */
+       snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+                               snd_dma_pci_data(sis->pci), 64*1024, 128*1024);
+
+       return 0;
+}
+
+static unsigned short sis_ac97_rw(struct sis7019 *sis, int codec, u32 cmd)
+{
+       unsigned long io = sis->ioport;
+       unsigned short val = 0xffff;
+       u16 status;
+       u16 rdy;
+       int count;
+       const static u16 codec_ready[3] = {
+               SIS_AC97_STATUS_CODEC_READY,
+               SIS_AC97_STATUS_CODEC2_READY,
+               SIS_AC97_STATUS_CODEC3_READY,
+       };
+
+       rdy = codec_ready[codec];
+
+
+       /* Get the AC97 semaphore -- software first, so we don't spin
+        * pounding out IO reads on the hardware semaphore...
+        */
+       mutex_lock(&sis->ac97_mutex);
+
+       count = 0xffff;
+       while ((inw(io + SIS_AC97_SEMA) & SIS_AC97_SEMA_BUSY) && --count)
+               udelay(1);
+
+       if (!count)
+               goto timeout;
+
+       /* ... and wait for any outstanding commands to complete ...
+        */
+       count = 0xffff;
+       do {
+               status = inw(io + SIS_AC97_STATUS);
+               if ((status & rdy) && !(status & SIS_AC97_STATUS_BUSY))
+                       break;
+
+               udelay(1);
+       } while (--count);
+
+       if (!count)
+               goto timeout_sema;
+
+       /* ... before sending our command and waiting for it to finish ...
+        */
+       outl(cmd, io + SIS_AC97_CMD);
+       udelay(10);
+
+       count = 0xffff;
+       while ((inw(io + SIS_AC97_STATUS) & SIS_AC97_STATUS_BUSY) && --count)
+               udelay(1);
+
+       /* ... and reading the results (if any).
+        */
+       val = inl(io + SIS_AC97_CMD) >> 16;
+
+timeout_sema:
+       outl(SIS_AC97_SEMA_RELEASE, io + SIS_AC97_SEMA);
+timeout:
+       mutex_unlock(&sis->ac97_mutex);
+
+       if (!count) {
+               printk(KERN_ERR "sis7019: ac97 codec %d timeout cmd 0x%08x\n",
+                                       codec, cmd);
+       }
+
+       return val;
+}
+
+static void sis_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+                               unsigned short val)
+{
+       const static u32 cmd[3] = {
+               SIS_AC97_CMD_CODEC_WRITE,
+               SIS_AC97_CMD_CODEC2_WRITE,
+               SIS_AC97_CMD_CODEC3_WRITE,
+       };
+       sis_ac97_rw(ac97->private_data, ac97->num,
+                       (val << 16) | (reg << 8) | cmd[ac97->num]);
+}
+
+static unsigned short sis_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+       const static u32 cmd[3] = {
+               SIS_AC97_CMD_CODEC_READ,
+               SIS_AC97_CMD_CODEC2_READ,
+               SIS_AC97_CMD_CODEC3_READ,
+       };
+       return sis_ac97_rw(ac97->private_data, ac97->num,
+                                       (reg << 8) | cmd[ac97->num]);
+}
+
+static int __devinit sis_mixer_create(struct sis7019 *sis)
+{
+       struct snd_ac97_bus *bus;
+       struct snd_ac97_template ac97;
+       static struct snd_ac97_bus_ops ops = {
+               .write = sis_ac97_write,
+               .read = sis_ac97_read,
+       };
+       int rc;
+
+       memset(&ac97, 0, sizeof(ac97));
+       ac97.private_data = sis;
+
+       rc = snd_ac97_bus(sis->card, 0, &ops, NULL, &bus);
+       if (!rc && sis->codecs_present & SIS_PRIMARY_CODEC_PRESENT)
+               rc = snd_ac97_mixer(bus, &ac97, &sis->ac97[0]);
+       ac97.num = 1;
+       if (!rc && (sis->codecs_present & SIS_SECONDARY_CODEC_PRESENT))
+               rc = snd_ac97_mixer(bus, &ac97, &sis->ac97[1]);
+       ac97.num = 2;
+       if (!rc && (sis->codecs_present & SIS_TERTIARY_CODEC_PRESENT))
+               rc = snd_ac97_mixer(bus, &ac97, &sis->ac97[2]);
+
+       /* If we return an error here, then snd_card_free() should
+        * free up any ac97 codecs that got created, as well as the bus.
+        */
+       return rc;
+}
+
+static void sis_free_suspend(struct sis7019 *sis)
+{
+       int i;
+
+       for (i = 0; i < SIS_SUSPEND_PAGES; i++)
+               kfree(sis->suspend_state[i]);
+}
+
+static int sis_chip_free(struct sis7019 *sis)
+{
+       /* Reset the chip, and disable all interrputs.
+        */
+       outl(SIS_GCR_SOFTWARE_RESET, sis->ioport + SIS_GCR);
+       udelay(10);
+       outl(0, sis->ioport + SIS_GCR);
+       outl(0, sis->ioport + SIS_GIER);
+
+       /* Now, free everything we allocated.
+        */
+       if (sis->irq >= 0)
+               free_irq(sis->irq, sis);
+
+       if (sis->ioaddr)
+               iounmap(sis->ioaddr);
+
+       pci_release_regions(sis->pci);
+       pci_disable_device(sis->pci);
+
+       sis_free_suspend(sis);
+       return 0;
+}
+
+static int sis_dev_free(struct snd_device *dev)
+{
+       struct sis7019 *sis = dev->device_data;
+       return sis_chip_free(sis);
+}
+
+static int sis_chip_init(struct sis7019 *sis)
+{
+       unsigned long io = sis->ioport;
+       void __iomem *ioaddr = sis->ioaddr;
+       u16 status;
+       int count;
+       int i;
+
+       /* Reset the audio controller
+        */
+       outl(SIS_GCR_SOFTWARE_RESET, io + SIS_GCR);
+       udelay(10);
+       outl(0, io + SIS_GCR);
+
+       /* Get the AC-link semaphore, and reset the codecs
+        */
+       count = 0xffff;
+       while ((inw(io + SIS_AC97_SEMA) & SIS_AC97_SEMA_BUSY) && --count)
+               udelay(1);
+
+       if (!count)
+               return -EIO;
+
+       outl(SIS_AC97_CMD_CODEC_COLD_RESET, io + SIS_AC97_CMD);
+       udelay(10);
+
+       count = 0xffff;
+       while ((inw(io + SIS_AC97_STATUS) & SIS_AC97_STATUS_BUSY) && --count)
+               udelay(1);
+
+       /* Now that we've finished the reset, find out what's attached.
+        */
+       status = inl(io + SIS_AC97_STATUS);
+       if (status & SIS_AC97_STATUS_CODEC_READY)
+               sis->codecs_present |= SIS_PRIMARY_CODEC_PRESENT;
+       if (status & SIS_AC97_STATUS_CODEC2_READY)
+               sis->codecs_present |= SIS_SECONDARY_CODEC_PRESENT;
+       if (status & SIS_AC97_STATUS_CODEC3_READY)
+               sis->codecs_present |= SIS_TERTIARY_CODEC_PRESENT;
+
+       /* All done, let go of the semaphore, and check for errors
+        */
+       outl(SIS_AC97_SEMA_RELEASE, io + SIS_AC97_SEMA);
+       if (!sis->codecs_present || !count)
+               return -EIO;
+
+       /* Let the hardware know that the audio driver is alive,
+        * and enable PCM slots on the AC-link for L/R playback (3 & 4) and
+        * record channels. We're going to want to use Variable Rate Audio
+        * for recording, to avoid needlessly resampling from 48kHZ.
+        */
+       outl(SIS_AC97_CONF_AUDIO_ALIVE, io + SIS_AC97_CONF);
+       outl(SIS_AC97_CONF_AUDIO_ALIVE | SIS_AC97_CONF_PCM_LR_ENABLE |
+               SIS_AC97_CONF_PCM_CAP_MIC_ENABLE |
+               SIS_AC97_CONF_PCM_CAP_LR_ENABLE |
+               SIS_AC97_CONF_CODEC_VRA_ENABLE, io + SIS_AC97_CONF);
+
+       /* All AC97 PCM slots should be sourced from sub-mixer 0.
+        */
+       outl(0, io + SIS_AC97_PSR);
+
+       /* There is only one valid DMA setup for a PCI environment.
+        */
+       outl(SIS_DMA_CSR_PCI_SETTINGS, io + SIS_DMA_CSR);
+
+       /* Reset the syncronization groups for all of the channels
+        * to be asyncronous. If we start doing SPDIF or 5.1 sound, etc.
+        * we'll need to change how we handle these. Until then, we just
+        * assign sub-mixer 0 to all playback channels, and avoid any
+        * attenuation on the audio.
+        */
+       outl(0, io + SIS_PLAY_SYNC_GROUP_A);
+       outl(0, io + SIS_PLAY_SYNC_GROUP_B);
+       outl(0, io + SIS_PLAY_SYNC_GROUP_C);
+       outl(0, io + SIS_PLAY_SYNC_GROUP_D);
+       outl(0, io + SIS_MIXER_SYNC_GROUP);
+
+       for (i = 0; i < 64; i++) {
+               writel(i, SIS_MIXER_START_ADDR(ioaddr, i));
+               writel(SIS_MIXER_RIGHT_NO_ATTEN | SIS_MIXER_LEFT_NO_ATTEN |
+                               SIS_MIXER_DEST_0, SIS_MIXER_ADDR(ioaddr, i));
+       }
+
+       /* Don't attenuate any audio set for the wave amplifier.
+        *
+        * FIXME: Maximum attenuation is set for the music amp, which will
+        * need to change if we start using the synth engine.
+        */
+       outl(0xffff0000, io + SIS_WEVCR);
+
+       /* Ensure that the wave engine is in normal operating mode.
+        */
+       outl(0, io + SIS_WECCR);
+
+       /* Go ahead and enable the DMA interrupts. They won't go live
+        * until we start a channel.
+        */
+       outl(SIS_GIER_AUDIO_PLAY_DMA_IRQ_ENABLE |
+               SIS_GIER_AUDIO_RECORD_DMA_IRQ_ENABLE, io + SIS_GIER);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int sis_suspend(struct pci_dev *pci, pm_message_t state)
+{
+       struct snd_card *card = pci_get_drvdata(pci);
+       struct sis7019 *sis = card->private_data;
+       void __iomem *ioaddr = sis->ioaddr;
+       int i;
+
+       snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+       snd_pcm_suspend_all(sis->pcm);
+       if (sis->codecs_present & SIS_PRIMARY_CODEC_PRESENT)
+               snd_ac97_suspend(sis->ac97[0]);
+       if (sis->codecs_present & SIS_SECONDARY_CODEC_PRESENT)
+               snd_ac97_suspend(sis->ac97[1]);
+       if (sis->codecs_present & SIS_TERTIARY_CODEC_PRESENT)
+               snd_ac97_suspend(sis->ac97[2]);
+
+       /* snd_pcm_suspend_all() stopped all channels, so we're quiescent.
+        */
+       if (sis->irq >= 0) {
+               synchronize_irq(sis->irq);
+               free_irq(sis->irq, sis);
+               sis->irq = -1;
+       }
+
+       /* Save the internal state away
+        */
+       for (i = 0; i < 4; i++) {
+               memcpy_fromio(sis->suspend_state[i], ioaddr, 4096);
+               ioaddr += 4096;
+       }
+
+       pci_disable_device(pci);
+       pci_save_state(pci);
+       pci_set_power_state(pci, pci_choose_state(pci, state));
+       return 0;
+}
+
+static int sis_resume(struct pci_dev *pci)
+{
+       struct snd_card *card = pci_get_drvdata(pci);
+       struct sis7019 *sis = card->private_data;
+       void __iomem *ioaddr = sis->ioaddr;
+       int i;
+
+       pci_set_power_state(pci, PCI_D0);
+       pci_restore_state(pci);
+
+       if (pci_enable_device(pci) < 0) {
+               printk(KERN_ERR "sis7019: unable to re-enable device\n");
+               goto error;
+       }
+
+       if (sis_chip_init(sis)) {
+               printk(KERN_ERR "sis7019: unable to re-init controller\n");
+               goto error;
+       }
+
+       if (request_irq(pci->irq, sis_interrupt, IRQF_DISABLED|IRQF_SHARED,
+                               card->shortname, sis)) {
+               printk(KERN_ERR "sis7019: unable to regain IRQ %d\n", pci->irq);
+               goto error;
+       }
+
+       /* Restore saved state, then clear out the page we use for the
+        * silence buffer.
+        */
+       for (i = 0; i < 4; i++) {
+               memcpy_toio(ioaddr, sis->suspend_state[i], 4096);
+               ioaddr += 4096;
+       }
+
+       memset(sis->suspend_state[0], 0, 4096);
+
+       sis->irq = pci->irq;
+       pci_set_master(pci);
+
+       if (sis->codecs_present & SIS_PRIMARY_CODEC_PRESENT)
+               snd_ac97_resume(sis->ac97[0]);
+       if (sis->codecs_present & SIS_SECONDARY_CODEC_PRESENT)
+               snd_ac97_resume(sis->ac97[1]);
+       if (sis->codecs_present & SIS_TERTIARY_CODEC_PRESENT)
+               snd_ac97_resume(sis->ac97[2]);
+
+       snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+       return 0;
+
+error:
+       snd_card_disconnect(card);
+       return -EIO;
+}
+#endif /* CONFIG_PM */
+
+static int sis_alloc_suspend(struct sis7019 *sis)
+{
+       int i;
+
+       /* We need 16K to store the internal wave engine state during a
+        * suspend, but we don't need it to be contiguous, so play nice
+        * with the memory system. We'll also use this area for a silence
+        * buffer.
+        */
+       for (i = 0; i < SIS_SUSPEND_PAGES; i++) {
+               sis->suspend_state[i] = kmalloc(4096, GFP_KERNEL);
+               if (!sis->suspend_state[i])
+                       return -ENOMEM;
+       }
+       memset(sis->suspend_state[0], 0, 4096);
+
+       return 0;
+}
+
+static int __devinit sis_chip_create(struct snd_card *card,
+                                       struct pci_dev *pci)
+{
+       struct sis7019 *sis = card->private_data;
+       struct voice *voice;
+       static struct snd_device_ops ops = {
+               .dev_free = sis_dev_free,
+       };
+       int rc;
+       int i;
+
+       rc = pci_enable_device(pci);
+       if (rc)
+               goto error_out;
+
+       if (pci_set_dma_mask(pci, DMA_30BIT_MASK) < 0) {
+               printk(KERN_ERR "sis7019: architecture does not support "
+                                       "30-bit PCI busmaster DMA");
+               goto error_out_enabled;
+       }
+
+       memset(sis, 0, sizeof(*sis));
+       mutex_init(&sis->ac97_mutex);
+       spin_lock_init(&sis->voice_lock);
+       sis->card = card;
+       sis->pci = pci;
+       sis->irq = -1;
+       sis->ioport = pci_resource_start(pci, 0);
+
+       rc = pci_request_regions(pci, "SiS7019");
+       if (rc) {
+               printk(KERN_ERR "sis7019: unable request regions\n");
+               goto error_out_enabled;
+       }
+
+       rc = -EIO;
+       sis->ioaddr = ioremap_nocache(pci_resource_start(pci, 1), 0x4000);
+       if (!sis->ioaddr) {
+               printk(KERN_ERR "sis7019: unable to remap MMIO, aborting\n");
+               goto error_out_cleanup;
+       }
+
+       rc = sis_alloc_suspend(sis);
+       if (rc < 0) {
+               printk(KERN_ERR "sis7019: unable to allocate state storage\n");
+               goto error_out_cleanup;
+       }
+
+       rc = sis_chip_init(sis);
+       if (rc)
+               goto error_out_cleanup;
+
+       if (request_irq(pci->irq, sis_interrupt, IRQF_DISABLED|IRQF_SHARED,
+                               card->shortname, sis)) {
+               printk(KERN_ERR "unable to allocate irq %d\n", sis->irq);
+               goto error_out_cleanup;
+       }
+
+       sis->irq = pci->irq;
+       pci_set_master(pci);
+
+       for (i = 0; i < 64; i++) {
+               voice = &sis->voices[i];
+               voice->num = i;
+               voice->ctrl_base = SIS_PLAY_DMA_ADDR(sis->ioaddr, i);
+               voice->wave_base = SIS_WAVE_ADDR(sis->ioaddr, i);
+       }
+
+       voice = &sis->capture_voice;
+       voice->flags = VOICE_CAPTURE;
+       voice->num = SIS_CAPTURE_CHAN_AC97_PCM_IN;
+       voice->ctrl_base = SIS_CAPTURE_DMA_ADDR(sis->ioaddr, voice->num);
+
+       rc = snd_device_new(card, SNDRV_DEV_LOWLEVEL, sis, &ops);
+       if (rc)
+               goto error_out_cleanup;
+
+       snd_card_set_dev(card, &pci->dev);
+
+       return 0;
+
+error_out_cleanup:
+       sis_chip_free(sis);
+
+error_out_enabled:
+       pci_disable_device(pci);
+
+error_out:
+       return rc;
+}
+
+static int __devinit snd_sis7019_probe(struct pci_dev *pci,
+                                       const struct pci_device_id *pci_id)
+{
+       struct snd_card *card;
+       struct sis7019 *sis;
+       int rc;
+
+       rc = -ENOENT;
+       if (!enable)
+               goto error_out;
+
+       rc = -ENOMEM;
+       card = snd_card_new(index, id, THIS_MODULE, sizeof(*sis));
+       if (!card)
+               goto error_out;
+
+       strcpy(card->driver, "SiS7019");
+       strcpy(card->shortname, "SiS7019");
+       rc = sis_chip_create(card, pci);
+       if (rc)
+               goto card_error_out;
+
+       sis = card->private_data;
+
+       rc = sis_mixer_create(sis);
+       if (rc)
+               goto card_error_out;
+
+       rc = sis_pcm_create(sis);
+       if (rc)
+               goto card_error_out;
+
+       snprintf(card->longname, sizeof(card->longname),
+                       "%s Audio Accelerator with %s at 0x%lx, irq %d",
+                       card->shortname, snd_ac97_get_short_name(sis->ac97[0]),
+                       sis->ioport, sis->irq);
+
+       rc = snd_card_register(card);
+       if (rc)
+               goto card_error_out;
+
+       pci_set_drvdata(pci, card);
+       return 0;
+
+card_error_out:
+       snd_card_free(card);
+
+error_out:
+       return rc;
+}
+
+static void __devexit snd_sis7019_remove(struct pci_dev *pci)
+{
+       snd_card_free(pci_get_drvdata(pci));
+       pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver sis7019_driver = {
+       .name = "SiS7019",
+       .id_table = snd_sis7019_ids,
+       .probe = snd_sis7019_probe,
+       .remove = __devexit_p(snd_sis7019_remove),
+
+#ifdef CONFIG_PM
+       .suspend = sis_suspend,
+       .resume = sis_resume,
+#endif
+};
+
+static int __init sis7019_init(void)
+{
+       return pci_register_driver(&sis7019_driver);
+}
+
+static void __exit sis7019_exit(void)
+{
+       pci_unregister_driver(&sis7019_driver);
+}
+
+module_init(sis7019_init);
+module_exit(sis7019_exit);
diff --git a/sound/pci/sis7019.h b/sound/pci/sis7019.h
new file mode 100644 (file)
index 0000000..013b673
--- /dev/null
@@ -0,0 +1,342 @@
+#ifndef __sis7019_h__
+#define __sis7019_h__
+
+/*
+ *  Definitions for SiS7019 Audio Accelerator
+ *
+ *  Copyright (C) 2004-2007, David Dillow
+ *  Written by David Dillow <dave@thedillows.org>
+ *  Inspired by the Trident 4D-WaveDX/NX driver.
+ *
+ *  All rights reserved.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, version 2.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+/* General Control Register */
+#define SIS_GCR                0x00
+#define                SIS_GCR_MACRO_POWER_DOWN                0x80000000
+#define                SIS_GCR_MODEM_ENABLE                    0x00010000
+#define                SIS_GCR_SOFTWARE_RESET                  0x00000001
+
+/* General Interrupt Enable Register */
+#define SIS_GIER       0x04
+#define                SIS_GIER_MODEM_TIMER_IRQ_ENABLE         0x00100000
+#define                SIS_GIER_MODEM_RX_DMA_IRQ_ENABLE        0x00080000
+#define                SIS_GIER_MODEM_TX_DMA_IRQ_ENABLE        0x00040000
+#define                SIS_GIER_AC97_GPIO1_IRQ_ENABLE          0x00020000
+#define                SIS_GIER_AC97_GPIO0_IRQ_ENABLE          0x00010000
+#define                SIS_GIER_AC97_SAMPLE_TIMER_IRQ_ENABLE   0x00000010
+#define                SIS_GIER_AUDIO_GLOBAL_TIMER_IRQ_ENABLE  0x00000008
+#define                SIS_GIER_AUDIO_RECORD_DMA_IRQ_ENABLE    0x00000004
+#define                SIS_GIER_AUDIO_PLAY_DMA_IRQ_ENABLE      0x00000002
+#define                SIS_GIER_AUDIO_WAVE_ENGINE_IRQ_ENABLE   0x00000001
+
+/* General Interrupt Status Register */
+#define SIS_GISR       0x08
+#define                SIS_GISR_MODEM_TIMER_IRQ_STATUS         0x00100000
+#define                SIS_GISR_MODEM_RX_DMA_IRQ_STATUS        0x00080000
+#define                SIS_GISR_MODEM_TX_DMA_IRQ_STATUS        0x00040000
+#define                SIS_GISR_AC97_GPIO1_IRQ_STATUS          0x00020000
+#define                SIS_GISR_AC97_GPIO0_IRQ_STATUS          0x00010000
+#define                SIS_GISR_AC97_SAMPLE_TIMER_IRQ_STATUS   0x00000010
+#define                SIS_GISR_AUDIO_GLOBAL_TIMER_IRQ_STATUS  0x00000008
+#define                SIS_GISR_AUDIO_RECORD_DMA_IRQ_STATUS    0x00000004
+#define                SIS_GISR_AUDIO_PLAY_DMA_IRQ_STATUS      0x00000002
+#define                SIS_GISR_AUDIO_WAVE_ENGINE_IRQ_STATUS   0x00000001
+
+/* DMA Control Register */
+#define SIS_DMA_CSR    0x10
+#define                SIS_DMA_CSR_PCI_SETTINGS                0x0000001d
+#define                SIS_DMA_CSR_CONCURRENT_ENABLE           0x00000200
+#define                SIS_DMA_CSR_PIPELINE_ENABLE             0x00000100
+#define                SIS_DMA_CSR_RX_DRAIN_ENABLE             0x00000010
+#define                SIS_DMA_CSR_RX_FILL_ENABLE              0x00000008
+#define                SIS_DMA_CSR_TX_DRAIN_ENABLE             0x00000004
+#define                SIS_DMA_CSR_TX_LOWPRI_FILL_ENABLE       0x00000002
+#define                SIS_DMA_CSR_TX_HIPRI_FILL_ENABLE        0x00000001
+
+/* Playback Channel Start Registers */
+#define SIS_PLAY_START_A_REG   0x14
+#define SIS_PLAY_START_B_REG   0x18
+
+/* Playback Channel Stop Registers */
+#define SIS_PLAY_STOP_A_REG    0x1c
+#define SIS_PLAY_STOP_B_REG    0x20
+
+/* Recording Channel Start Register */
+#define SIS_RECORD_START_REG   0x24
+
+/* Recording Channel Stop Register */
+#define SIS_RECORD_STOP_REG    0x28
+
+/* Playback Interrupt Status Registers */
+#define SIS_PISR_A     0x2c
+#define SIS_PISR_B     0x30
+
+/* Recording Interrupt Status Register */
+#define SIS_RISR       0x34
+
+/* AC97 AC-link Playback Source Register */
+#define SIS_AC97_PSR   0x40
+#define                SIS_AC97_PSR_MODEM_HEADSET_SRC_MIXER    0x0f000000
+#define                SIS_AC97_PSR_MODEM_LINE2_SRC_MIXER      0x00f00000
+#define                SIS_AC97_PSR_MODEM_LINE1_SRC_MIXER      0x000f0000
+#define                SIS_AC97_PSR_PCM_LFR_SRC_MIXER          0x0000f000
+#define                SIS_AC97_PSR_PCM_SURROUND_SRC_MIXER     0x00000f00
+#define                SIS_AC97_PSR_PCM_CENTER_SRC_MIXER       0x000000f0
+#define                SIS_AC97_PSR_PCM_LR_SRC_MIXER           0x0000000f
+
+/* AC97 AC-link Command Register */
+#define SIS_AC97_CMD   0x50
+#define        SIS_AC97_CMD_DATA_MASK                  0xffff0000
+#define                SIS_AC97_CMD_REG_MASK                   0x0000ff00
+#define                SIS_AC97_CMD_CODEC3_READ                0x0000000d
+#define                SIS_AC97_CMD_CODEC3_WRITE               0x0000000c
+#define                SIS_AC97_CMD_CODEC2_READ                0x0000000b
+#define                SIS_AC97_CMD_CODEC2_WRITE               0x0000000a
+#define                SIS_AC97_CMD_CODEC_READ                 0x00000009
+#define                SIS_AC97_CMD_CODEC_WRITE                0x00000008
+#define                SIS_AC97_CMD_CODEC_WARM_RESET           0x00000005
+#define                SIS_AC97_CMD_CODEC_COLD_RESET           0x00000004
+#define                SIS_AC97_CMD_DONE                       0x00000000
+
+/* AC97 AC-link Semaphore Register */
+#define SIS_AC97_SEMA  0x54
+#define                SIS_AC97_SEMA_BUSY                      0x00000001
+#define                SIS_AC97_SEMA_RELEASE                   0x00000000
+
+/* AC97 AC-link Status Register */
+#define SIS_AC97_STATUS        0x58
+#define                SIS_AC97_STATUS_AUDIO_D2_INACT_SECS     0x03f00000
+#define                SIS_AC97_STATUS_MODEM_ALIVE             0x00002000
+#define                SIS_AC97_STATUS_AUDIO_ALIVE             0x00001000
+#define                SIS_AC97_STATUS_CODEC3_READY            0x00000400
+#define                SIS_AC97_STATUS_CODEC2_READY            0x00000200
+#define                SIS_AC97_STATUS_CODEC_READY             0x00000100
+#define                SIS_AC97_STATUS_WARM_RESET              0x00000080
+#define                SIS_AC97_STATUS_COLD_RESET              0x00000040
+#define                SIS_AC97_STATUS_POWERED_DOWN            0x00000020
+#define                SIS_AC97_STATUS_NORMAL                  0x00000010
+#define                SIS_AC97_STATUS_READ_EXPIRED            0x00000004
+#define                SIS_AC97_STATUS_SEMAPHORE               0x00000002
+#define                SIS_AC97_STATUS_BUSY                    0x00000001
+
+/* AC97 AC-link Audio Configuration Register */
+#define SIS_AC97_CONF  0x5c
+#define                SIS_AC97_CONF_AUDIO_ALIVE               0x80000000
+#define                SIS_AC97_CONF_WARM_RESET_ENABLE         0x40000000
+#define                SIS_AC97_CONF_PR6_ENABLE                0x20000000
+#define                SIS_AC97_CONF_PR5_ENABLE                0x10000000
+#define                SIS_AC97_CONF_PR4_ENABLE                0x08000000
+#define                SIS_AC97_CONF_PR3_ENABLE                0x04000000
+#define                SIS_AC97_CONF_PR2_PR7_ENABLE            0x02000000
+#define                SIS_AC97_CONF_PR0_PR1_ENABLE            0x01000000
+#define                SIS_AC97_CONF_AUTO_PM_ENABLE            0x00800000
+#define                SIS_AC97_CONF_PCM_LFE_ENABLE            0x00080000
+#define                SIS_AC97_CONF_PCM_SURROUND_ENABLE       0x00040000
+#define                SIS_AC97_CONF_PCM_CENTER_ENABLE         0x00020000
+#define                SIS_AC97_CONF_PCM_LR_ENABLE             0x00010000
+#define                SIS_AC97_CONF_PCM_CAP_MIC_ENABLE        0x00002000
+#define                SIS_AC97_CONF_PCM_CAP_LR_ENABLE         0x00001000
+#define                SIS_AC97_CONF_PCM_CAP_MIC_FROM_CODEC3   0x00000200
+#define                SIS_AC97_CONF_PCM_CAP_LR_FROM_CODEC3    0x00000100
+#define                SIS_AC97_CONF_CODEC3_PM_VRM             0x00000080
+#define                SIS_AC97_CONF_CODEC_PM_VRM              0x00000040
+#define                SIS_AC97_CONF_CODEC3_VRA_ENABLE         0x00000020
+#define                SIS_AC97_CONF_CODEC_VRA_ENABLE          0x00000010
+#define                SIS_AC97_CONF_CODEC3_PM_EAC             0x00000008
+#define                SIS_AC97_CONF_CODEC_PM_EAC              0x00000004
+#define                SIS_AC97_CONF_CODEC3_EXISTS             0x00000002
+#define                SIS_AC97_CONF_CODEC_EXISTS              0x00000001
+
+/* Playback Channel Sync Group registers */
+#define SIS_PLAY_SYNC_GROUP_A  0x80
+#define SIS_PLAY_SYNC_GROUP_B  0x84
+#define SIS_PLAY_SYNC_GROUP_C  0x88
+#define SIS_PLAY_SYNC_GROUP_D  0x8c
+#define SIS_MIXER_SYNC_GROUP   0x90
+
+/* Wave Engine Config and Control Register */
+#define SIS_WECCR      0xa0
+#define                SIS_WECCR_TESTMODE_MASK                 0x00300000
+#define                        SIS_WECCR_TESTMODE_NORMAL               0x00000000
+#define                        SIS_WECCR_TESTMODE_BYPASS_NSO_ALPHA     0x00100000
+#define                        SIS_WECCR_TESTMODE_BYPASS_FC            0x00200000
+#define                        SIS_WECCR_TESTMODE_BYPASS_WOL           0x00300000
+#define                SIS_WECCR_RESONANCE_DELAY_MASK          0x00060000
+#define                        SIS_WECCR_RESONANCE_DELAY_NONE          0x00000000
+#define                        SIS_WECCR_RESONANCE_DELAY_FC_1F00       0x00020000
+#define                        SIS_WECCR_RESONANCE_DELAY_FC_1E00       0x00040000
+#define                        SIS_WECCR_RESONANCE_DELAY_FC_1C00       0x00060000
+#define                SIS_WECCR_IGNORE_CHANNEL_PARMS          0x00010000
+#define                SIS_WECCR_COMMAND_CHANNEL_ID_MASK       0x0003ff00
+#define                SIS_WECCR_COMMAND_MASK                  0x00000007
+#define                        SIS_WECCR_COMMAND_NONE                  0x00000000
+#define                        SIS_WECCR_COMMAND_DONE                  0x00000000
+#define                        SIS_WECCR_COMMAND_PAUSE                 0x00000001
+#define                        SIS_WECCR_COMMAND_TOGGLE_VEG            0x00000002
+#define                        SIS_WECCR_COMMAND_TOGGLE_MEG            0x00000003
+#define                        SIS_WECCR_COMMAND_TOGGLE_VEG_MEG        0x00000004
+
+/* Wave Engine Volume Control Register */
+#define SIS_WEVCR      0xa4
+#define                SIS_WEVCR_LEFT_MUSIC_ATTENUATION_MASK   0xff000000
+#define                SIS_WEVCR_RIGHT_MUSIC_ATTENUATION_MASK  0x00ff0000
+#define                SIS_WEVCR_LEFT_WAVE_ATTENUATION_MASK    0x0000ff00
+#define                SIS_WEVCR_RIGHT_WAVE_ATTENUATION_MASK   0x000000ff
+
+/* Wave Engine Interrupt Status Registers */
+#define SIS_WEISR_A    0xa8
+#define SIS_WEISR_B    0xac
+
+
+/* Playback DMA parameters (paramter RAM) */
+#define SIS_PLAY_DMA_OFFSET    0x0000
+#define SIS_PLAY_DMA_SIZE      0x10
+#define SIS_PLAY_DMA_ADDR(addr, num) \
+       ((num * SIS_PLAY_DMA_SIZE) + (addr) + SIS_PLAY_DMA_OFFSET)
+
+#define SIS_PLAY_DMA_FORMAT_CSO        0x00
+#define                SIS_PLAY_DMA_FORMAT_UNSIGNED    0x00080000
+#define                SIS_PLAY_DMA_FORMAT_8BIT        0x00040000
+#define                SIS_PLAY_DMA_FORMAT_MONO        0x00020000
+#define                SIS_PLAY_DMA_CSO_MASK           0x0000ffff
+#define SIS_PLAY_DMA_BASE      0x04
+#define SIS_PLAY_DMA_CONTROL   0x08
+#define                SIS_PLAY_DMA_STOP_AT_SSO        0x04000000
+#define                SIS_PLAY_DMA_RELEASE            0x02000000
+#define                SIS_PLAY_DMA_LOOP               0x01000000
+#define                SIS_PLAY_DMA_INTR_AT_SSO        0x00080000
+#define                SIS_PLAY_DMA_INTR_AT_ESO        0x00040000
+#define                SIS_PLAY_DMA_INTR_AT_LEO        0x00020000
+#define                SIS_PLAY_DMA_INTR_AT_MLP        0x00010000
+#define                SIS_PLAY_DMA_LEO_MASK           0x0000ffff
+#define SIS_PLAY_DMA_SSO_ESO   0x0c
+#define                SIS_PLAY_DMA_SSO_MASK           0xffff0000
+#define                SIS_PLAY_DMA_ESO_MASK           0x0000ffff
+
+/* Capture DMA parameters (paramter RAM) */
+#define SIS_CAPTURE_DMA_OFFSET 0x0800
+#define SIS_CAPTURE_DMA_SIZE   0x10
+#define SIS_CAPTURE_DMA_ADDR(addr, num) \
+       ((num * SIS_CAPTURE_DMA_SIZE) + (addr) + SIS_CAPTURE_DMA_OFFSET)
+
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_0     0
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_1     1
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_2     2
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_3     3
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_4     4
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_5     5
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_6     6
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_7     7
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_8     8
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_9     9
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_10    10
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_11    11
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_12    12
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_13    13
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_14    14
+#define        SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_15    15
+#define        SIS_CAPTURE_CHAN_AC97_PCM_IN            16
+#define        SIS_CAPTURE_CHAN_AC97_MIC_IN            17
+#define        SIS_CAPTURE_CHAN_AC97_LINE1_IN          18
+#define        SIS_CAPTURE_CHAN_AC97_LINE2_IN          19
+#define        SIS_CAPTURE_CHAN_AC97_HANDSE_IN         20
+
+#define SIS_CAPTURE_DMA_FORMAT_CSO     0x00
+#define                SIS_CAPTURE_DMA_MONO_MODE_MASK  0xc0000000
+#define                SIS_CAPTURE_DMA_MONO_MODE_AVG   0x00000000
+#define                SIS_CAPTURE_DMA_MONO_MODE_LEFT  0x40000000
+#define                SIS_CAPTURE_DMA_MONO_MODE_RIGHT 0x80000000
+#define                SIS_CAPTURE_DMA_FORMAT_UNSIGNED 0x00080000
+#define                SIS_CAPTURE_DMA_FORMAT_8BIT     0x00040000
+#define                SIS_CAPTURE_DMA_FORMAT_MONO     0x00020000
+#define                SIS_CAPTURE_DMA_CSO_MASK                0x0000ffff
+#define SIS_CAPTURE_DMA_BASE           0x04
+#define SIS_CAPTURE_DMA_CONTROL                0x08
+#define                SIS_CAPTURE_DMA_STOP_AT_SSO     0x04000000
+#define                SIS_CAPTURE_DMA_RELEASE         0x02000000
+#define                SIS_CAPTURE_DMA_LOOP            0x01000000
+#define                SIS_CAPTURE_DMA_INTR_AT_LEO     0x00020000
+#define                SIS_CAPTURE_DMA_INTR_AT_MLP     0x00010000
+#define                SIS_CAPTURE_DMA_LEO_MASK                0x0000ffff
+#define SIS_CAPTURE_DMA_RESERVED       0x0c
+
+
+/* Mixer routing list start pointer (parameter RAM) */
+#define SIS_MIXER_START_OFFSET 0x1000
+#define SIS_MIXER_START_SIZE   0x04
+#define SIS_MIXER_START_ADDR(addr, num) \
+       ((num * SIS_MIXER_START_SIZE) + (addr) + SIS_MIXER_START_OFFSET)
+
+#define SIS_MIXER_START_MASK   0x0000007f
+
+/* Mixer routing table (parameter RAM) */
+#define SIS_MIXER_OFFSET       0x1400
+#define SIS_MIXER_SIZE         0x04
+#define SIS_MIXER_ADDR(addr, num) \
+       ((num * SIS_MIXER_SIZE) + (addr) + SIS_MIXER_OFFSET)
+
+#define SIS_MIXER_RIGHT_ATTENUTATION_MASK      0xff000000
+#define        SIS_MIXER_RIGHT_NO_ATTEN                0xff000000
+#define SIS_MIXER_LEFT_ATTENUTATION_MASK       0x00ff0000
+#define        SIS_MIXER_LEFT_NO_ATTEN                 0x00ff0000
+#define SIS_MIXER_NEXT_ENTRY_MASK              0x00007f00
+#define        SIS_MIXER_NEXT_ENTRY_NONE               0x00000000
+#define SIS_MIXER_DEST_MASK                    0x0000007f
+#define        SIS_MIXER_DEST_0                        0x00000020
+#define        SIS_MIXER_DEST_1                        0x00000021
+#define        SIS_MIXER_DEST_2                        0x00000022
+#define        SIS_MIXER_DEST_3                        0x00000023
+#define        SIS_MIXER_DEST_4                        0x00000024
+#define        SIS_MIXER_DEST_5                        0x00000025
+#define        SIS_MIXER_DEST_6                        0x00000026
+#define        SIS_MIXER_DEST_7                        0x00000027
+#define        SIS_MIXER_DEST_8                        0x00000028
+#define        SIS_MIXER_DEST_9                        0x00000029
+#define        SIS_MIXER_DEST_10                       0x0000002a
+#define        SIS_MIXER_DEST_11                       0x0000002b
+#define        SIS_MIXER_DEST_12                       0x0000002c
+#define        SIS_MIXER_DEST_13                       0x0000002d
+#define        SIS_MIXER_DEST_14                       0x0000002e
+#define        SIS_MIXER_DEST_15                       0x0000002f
+
+/* Wave Engine Control Parameters (parameter RAM) */
+#define SIS_WAVE_OFFSET                0x2000
+#define SIS_WAVE_SIZE          0x40
+#define SIS_WAVE_ADDR(addr, num) \
+       ((num * SIS_WAVE_SIZE) + (addr) + SIS_WAVE_OFFSET)
+
+#define SIS_WAVE_GENERAL               0x00
+#define                SIS_WAVE_GENERAL_WAVE_VOLUME                    0x80000000
+#define                SIS_WAVE_GENERAL_MUSIC_VOLUME                   0x00000000
+#define                SIS_WAVE_GENERAL_VOLUME_MASK                    0x7f000000
+#define SIS_WAVE_GENERAL_ARTICULATION  0x04
+#define                SIS_WAVE_GENERAL_ARTICULATION_DELTA_MASK        0x3fff0000
+#define SIS_WAVE_ARTICULATION          0x08
+#define SIS_WAVE_TIMER                 0x0c
+#define SIS_WAVE_GENERATOR             0x10
+#define SIS_WAVE_CHANNEL_CONTROL       0x14
+#define                SIS_WAVE_CHANNEL_CONTROL_FIRST_SAMPLE           0x80000000
+#define                SIS_WAVE_CHANNEL_CONTROL_AMP_ENABLE             0x40000000
+#define                SIS_WAVE_CHANNEL_CONTROL_FILTER_ENABLE          0x20000000
+#define                SIS_WAVE_CHANNEL_CONTROL_INTERPOLATE_ENABLE     0x10000000
+#define SIS_WAVE_LFO_EG_CONTROL                0x18
+#define SIS_WAVE_LFO_EG_CONTROL_2      0x1c
+#define SIS_WAVE_LFO_EG_CONTROL_3      0x20
+#define SIS_WAVE_LFO_EG_CONTROL_4      0x24
+
+#endif /* __sis7019_h__ */