Hi,

A patch for the via82cxxx_audio sound driver against 2.4.1-pre8.
It includes:

1. Support for variable fragment size and variable fragment number
2. Fixes for the SPEED, STEREO, CHANNELS, FMT ioctls when in read & write
mode
2.1 Mmaped sound is now fully functional.

The patch is fairly tested (xmms, quake3, oss-test) but it might
still have some bugs. Also there is almost no documentation update...
sorry.

The remaining issue with the driver (that I can see) are the
click/pops that happen when playback is started/stopped. I guess some
of the channel setting/resetting is just a bit too harsh.

Rui Sousa
--- linux-2.4.1-pre8/drivers/sound/via82cxxx_audio.c    Sat Dec 30 05:25:47 2000
+++ linux-2.4.1-pre8.new/drivers/sound/via82cxxx_audio.c        Thu Jan 18 21:22:17 
+2001
@@ -76,8 +76,16 @@
 #define VIA_COUNTER_LIMIT      100000
 
 /* size of DMA buffers */
-#define VIA_DMA_BUFFERS                16
-#define VIA_DMA_BUF_SIZE       PAGE_SIZE
+#define VIA_MAX_BUFFER_DMA_PAGES       32
+
+/* buffering default values in ms */
+#define VIA_DEFAULT_FRAG_TIME          20
+#define VIA_DEFAULT_BUFFER_TIME                500
+
+#define VIA_MAX_FRAG_SIZE              PAGE_SIZE
+#define VIA_MIN_FRAG_SIZE              64
+
+#define VIA_MIN_FRAG_NUMBER            2       
 
 #ifndef AC97_PCM_LR_ADC_RATE
 #  define AC97_PCM_LR_ADC_RATE AC97_PCM_LR_DAC_RATE
@@ -102,7 +110,6 @@
 #define VIA_BASE0_PCM_OUT_CHAN_STATUS 0x00
 #define VIA_BASE0_PCM_OUT_CHAN_CTRL    0x01
 #define VIA_BASE0_PCM_OUT_CHAN_TYPE    0x02
-#define VIA_BASE0_PCM_OUT_BLOCK_COUNT  0x0C
 
 #define VIA_BASE0_PCM_IN_CHAN          0x10 /* input PCM from user */
 #define VIA_BASE0_PCM_IN_CHAN_STATUS   0x10
@@ -114,6 +121,7 @@
 #define VIA_PCM_CONTROL                        0x01
 #define VIA_PCM_TYPE                   0x02
 #define VIA_PCM_TABLE_ADDR             0x04
+#define VIA_PCM_BLOCK_COUNT            0x0C
 
 /* XXX unused DMA channel for FM PCM data */
 #define VIA_BASE0_FM_OUT_CHAN          0x20
@@ -223,14 +231,14 @@
 };
 
 
-struct via_sgd_data {
+struct via_buffer_pgtbl {
        dma_addr_t handle;
        void *cpuaddr;
 };
 
 
 struct via_channel {
-       atomic_t n_bufs;
+       atomic_t n_frags;
        atomic_t hw_ptr;
        wait_queue_head_t wait;
 
@@ -246,11 +254,14 @@
        u8 pcm_fmt;             /* VIA_PCM_FMT_xxx */
 
        unsigned rate;          /* sample rate */
+       unsigned int frag_size;
+       unsigned int frag_number;
 
        volatile struct via_sgd_table *sgtable;
        dma_addr_t sgt_handle;
 
-       struct via_sgd_data sgbuf [VIA_DMA_BUFFERS];
+       unsigned int page_number;
+       struct via_buffer_pgtbl pgtbl[VIA_MAX_BUFFER_DMA_PAGES];
 
        long iobase;
 
@@ -301,17 +312,16 @@
 static int via_dsp_ioctl (struct inode *inode, struct file *file, unsigned int cmd, 
unsigned long arg);
 static int via_dsp_open (struct inode *inode, struct file *file);
 static int via_dsp_release(struct inode *inode, struct file *file);
-#ifdef VIA_SUPPORT_MMAP
 static int via_dsp_mmap(struct file *file, struct vm_area_struct *vma);
-#endif
 
 static u16 via_ac97_read_reg (struct ac97_codec *codec, u8 reg);
 static void via_ac97_write_reg (struct ac97_codec *codec, u8 reg, u16 value);
 static u8 via_ac97_wait_idle (struct via_info *card);
 
 static void via_chan_free (struct via_info *card, struct via_channel *chan);
-static void via_chan_clear (struct via_channel *chan);
+static void via_chan_clear (struct via_info *card, struct via_channel *chan);
 static void via_chan_pcm_fmt (struct via_channel *chan, int reset);
+static void via_chan_buffer_free (struct via_info *card, struct via_channel *chan);
 
 #ifdef VIA_PROC_FS
 static int via_init_proc (void);
@@ -569,25 +579,53 @@
        chan->pcm_fmt = VIA_PCM_FMT_MASK;
        chan->is_enabled = 1;
 
-       if (chan->is_record)
-               atomic_set (&chan->n_bufs, 0);
-       else
-               atomic_set (&chan->n_bufs, VIA_DMA_BUFFERS);
+       chan->frag_number = 0;
+        chan->frag_size = 0;
+       atomic_set(&chan->n_frags, 0);
        atomic_set (&chan->hw_ptr, 0);
 }
 
+/**
+ *      via_chan_init - Initialize PCM channel
+ *      @card: Private audio chip info
+ *      @chan: Channel to be initialized
+ *
+ *      Performs some of the preparations necessary to begin
+ *      using a PCM channel.
+ *
+ *      Currently the preparations consist in 
+ *      setting the
+ *      PCM channel to a known state.
+ */
+
+
+static void via_chan_init (struct via_info *card, struct via_channel *chan)
+{
+
+        DPRINTK ("ENTER\n");
+
+       /* bzero channel structure, and init members to defaults */
+        via_chan_init_defaults (card, chan);
+
+        /* stop any existing channel output */
+        via_chan_clear (card, chan);
+        via_chan_status_clear (chan->iobase);
+        via_chan_pcm_fmt (chan, 1);
+
+       DPRINTK ("EXIT\n");
+}
 
 /**
- *     via_chan_init - Initialize PCM channel
+ *     via_chan_buffer_init - Initialize PCM channel buffer
  *     @card: Private audio chip info
  *     @chan: Channel to be initialized
  *
- *     Performs all the preparations necessary to begin
+ *     Performs some of the preparations necessary to begin
  *     using a PCM channel.
  *
  *     Currently the preparations include allocating the
- *     scatter-gather DMA table and buffers, setting the
- *     PCM channel to a known state, and passing the
+ *     scatter-gather DMA table and buffers,
+ *     and passing the
  *     address of the DMA table to the hardware.
  *
  *     Note that special care is taken when passing the
@@ -596,18 +634,21 @@
  *     always "take" the address.
  */
 
-static int via_chan_init (struct via_info *card, struct via_channel *chan)
+static int via_chan_buffer_init (struct via_info *card, struct via_channel *chan)
 {
+       int page, offset;
        int i;
 
        DPRINTK ("ENTER\n");
 
-       /* bzero channel structure, and init members to defaults */
-       via_chan_init_defaults (card, chan);
+       if (chan->sgtable != NULL) {
+               DPRINTK ("EXIT\n");
+               return 0;
+       }
 
        /* alloc DMA-able memory for scatter-gather table */
        chan->sgtable = pci_alloc_consistent (card->pdev,
-               (sizeof (struct via_sgd_table) * VIA_DMA_BUFFERS),
+               (sizeof (struct via_sgd_table) * chan->frag_number),
                &chan->sgt_handle);
        if (!chan->sgtable) {
                printk (KERN_ERR PFX "DMA table alloc fail, aborting\n");
@@ -616,45 +657,54 @@
        }
 
        memset ((void*)chan->sgtable, 0,
-               (sizeof (struct via_sgd_table) * VIA_DMA_BUFFERS));
+               (sizeof (struct via_sgd_table) * chan->frag_number));
 
        /* alloc DMA-able memory for scatter-gather buffers */
-       for (i = 0; i < VIA_DMA_BUFFERS; i++) {
-               chan->sgbuf[i].cpuaddr =
-                       pci_alloc_consistent (card->pdev, VIA_DMA_BUF_SIZE,
-                                             &chan->sgbuf[i].handle);
 
-               if (!chan->sgbuf[i].cpuaddr)
-                       goto err_out_nomem;
+       chan->page_number = (chan->frag_number * chan->frag_size) / PAGE_SIZE + 
+                           (((chan->frag_number * chan->frag_size) % PAGE_SIZE) ? 1 : 
+0);
+
+       for (i = 0; i < chan->page_number; i++) {
+               chan->pgtbl[i].cpuaddr = pci_alloc_consistent (card->pdev, PAGE_SIZE,
+                                             &chan->pgtbl[i].handle);
 
-               if (i < (VIA_DMA_BUFFERS - 1))
-                       chan->sgtable[i].count = cpu_to_le32 (VIA_DMA_BUF_SIZE | 
VIA_FLAG);
-               else
-                       chan->sgtable[i].count = cpu_to_le32 (VIA_DMA_BUF_SIZE | 
VIA_EOL);
-               chan->sgtable[i].addr = cpu_to_le32 (chan->sgbuf[i].handle);
+               if (!chan->pgtbl[i].cpuaddr) {
+                       chan->page_number = i;
+                       goto err_out_nomem;
+               }
 
 #ifndef VIA_NDEBUG
-               memset (chan->sgbuf[i].cpuaddr, 0xBC, VIA_DMA_BUF_SIZE);
+                memset (chan->pgtbl[i].cpuaddr, 0xBC, chan->frag_size);
 #endif
 
 #if 1
-               DPRINTK ("dmabuf #%d (h=%lx, 32(h)=%lx, v2p=%lx, a=%p)\n",
-                        i, (long)chan->sgbuf[i].handle,
-                        (long)chan->sgtable[i].addr,
-                        virt_to_phys(chan->sgbuf[i].cpuaddr),
-                        chan->sgbuf[i].cpuaddr);
+                DPRINTK ("dmabuf_pg #%d (h=%lx, v2p=%lx, a=%p)\n",
+                       i, (long)chan->pgtbl[i].handle,
+                       virt_to_phys(chan->pgtbl[i].cpuaddr),
+                       chan->pgtbl[i].cpuaddr);
 #endif
-
-               assert ((VIA_DMA_BUF_SIZE % PAGE_SIZE) == 0);
        }
 
-       /* stop any existing channel output */
-       via_chan_clear (chan);
-       via_chan_status_clear (chan->iobase);
-       via_chan_pcm_fmt (chan, 1);
+       for (i = 0; i < chan->frag_number; i++) {
+
+               page = i / (PAGE_SIZE / chan->frag_size);
+               offset = (i % (PAGE_SIZE / chan->frag_size)) * chan->frag_size;
+
+               chan->sgtable[i].count = cpu_to_le32 (chan->frag_size | VIA_FLAG);
+               chan->sgtable[i].addr = cpu_to_le32 (chan->pgtbl[page].handle + 
+offset);
+
+#if 1
+               DPRINTK ("dmabuf #%d (32(h)=%lx)\n",
+                        i,
+                        (long)chan->sgtable[i].addr);
+#endif
+       }       
+
+       /* overwrite the last buffer information */
+       chan->sgtable[chan->frag_number - 1].count = cpu_to_le32 (chan->frag_size | 
+VIA_EOL);
 
        /* set location of DMA-able scatter-gather info table */
-       DPRINTK("outl (0x%X, 0x%04lX)\n",
+       DPRINTK ("outl (0x%X, 0x%04lX)\n",
                cpu_to_le32 (chan->sgt_handle),
                chan->iobase + VIA_PCM_TABLE_ADDR);
 
@@ -664,7 +714,7 @@
        udelay (20);
        via_ac97_wait_idle (card);
 
-       DPRINTK("inl (0x%lX) = %x\n",
+       DPRINTK ("inl (0x%lX) = %x\n",
                chan->iobase + VIA_PCM_TABLE_ADDR,
                inl(chan->iobase + VIA_PCM_TABLE_ADDR));
 
@@ -673,7 +723,7 @@
 
 err_out_nomem:
        printk (KERN_ERR PFX "DMA buffer alloc fail, aborting\n");
-       via_chan_free (card, chan);
+       via_chan_buffer_free (card, chan);
        DPRINTK ("EXIT\n");
        return -ENOMEM;
 }
@@ -695,8 +745,6 @@
 
 static void via_chan_free (struct via_info *card, struct via_channel *chan)
 {
-       int i;
-
        DPRINTK ("ENTER\n");
 
        synchronize_irq();
@@ -710,23 +758,33 @@
 
        spin_unlock_irq (&card->lock);
 
+       DPRINTK ("EXIT\n");
+}
+
+static void via_chan_buffer_free (struct via_info *card, struct via_channel *chan)
+{
+       int i;
+
+        DPRINTK ("ENTER\n");
+
        /* zero location of DMA-able scatter-gather info table */
        via_ac97_wait_idle(card);
        outl (0, chan->iobase + VIA_PCM_TABLE_ADDR);
 
-       for (i = 0; i < VIA_DMA_BUFFERS; i++)
-               if (chan->sgbuf[i].cpuaddr) {
-                       assert ((VIA_DMA_BUF_SIZE % PAGE_SIZE) == 0);
-                       pci_free_consistent (card->pdev, VIA_DMA_BUF_SIZE,
-                                            chan->sgbuf[i].cpuaddr,
-                                            chan->sgbuf[i].handle);
-                       chan->sgbuf[i].cpuaddr = NULL;
-                       chan->sgbuf[i].handle = 0;
+       for (i = 0; i < chan->page_number; i++)
+               if (chan->pgtbl[i].cpuaddr) {
+                       pci_free_consistent (card->pdev, PAGE_SIZE,
+                                            chan->pgtbl[i].cpuaddr,
+                                            chan->pgtbl[i].handle);
+                       chan->pgtbl[i].cpuaddr = NULL;
+                       chan->pgtbl[i].handle = 0;
                }
 
+       chan->page_number = 0;
+
        if (chan->sgtable) {
                pci_free_consistent (card->pdev,
-                       (sizeof (struct via_sgd_table) * VIA_DMA_BUFFERS),
+                       (sizeof (struct via_sgd_table) * chan->frag_number),
                        (void*)chan->sgtable, chan->sgt_handle);
                chan->sgtable = NULL;
        }
@@ -771,11 +829,11 @@
        if (!chan->is_record)
                chan->pcm_fmt |= VIA_CHAN_TYPE_INT_SELECT;
 
-       outb (chan->pcm_fmt, chan->iobase + 2);
+       outb (chan->pcm_fmt, chan->iobase + VIA_PCM_TYPE);
 
        DPRINTK ("EXIT, pcm_fmt = 0x%02X, reg = 0x%02X\n",
                 chan->pcm_fmt,
-                inb (chan->iobase + 2));
+                inb (chan->iobase + VIA_PCM_TYPE));
 }
 
 
@@ -787,10 +845,11 @@
  *     all software pointers which track DMA operation.
  */
 
-static void via_chan_clear (struct via_channel *chan)
+static void via_chan_clear (struct via_info *card, struct via_channel *chan)
 {
        DPRINTK ("ENTER\n");
        via_chan_stop (chan->iobase);
+       via_chan_buffer_free(card, chan);
        chan->is_active = 0;
        chan->is_mapped = 0;
        chan->is_enabled = 1;
@@ -798,10 +857,6 @@
        chan->sw_ptr = 0;
        chan->n_irqs = 0;
        atomic_set (&chan->hw_ptr, 0);
-       if (chan->is_record)
-               atomic_set (&chan->n_bufs, 0);
-       else
-               atomic_set (&chan->n_bufs, VIA_DMA_BUFFERS);
        DPRINTK ("EXIT\n");
 }
 
@@ -826,7 +881,7 @@
 {
        DPRINTK ("ENTER, requested rate = %d\n", val);
 
-       via_chan_clear (chan);
+       via_chan_clear (card, chan);
 
        val = via_set_rate (&card->ac97, chan, val);
 
@@ -858,7 +913,7 @@
                 val == AFMT_S16_LE ? "AFMT_S16_LE" :
                 "unknown");
 
-       via_chan_clear (chan);
+       via_chan_clear (card, chan);
 
        assert (val != AFMT_QUERY); /* this case is handled elsewhere */
 
@@ -907,7 +962,7 @@
 {
        DPRINTK ("ENTER, channels = %d\n", val);
 
-       via_chan_clear (chan);
+       via_chan_clear (card, chan);
 
        switch (val) {
 
@@ -934,6 +989,78 @@
        return val;
 }
 
+static int via_chan_set_buffering (struct via_info *card,
+                                struct via_channel *chan, int val)
+{
+       int shift;
+
+        DPRINTK ("ENTER\n");
+
+       /* in both cases the buffer cannot be changed */
+       if (chan->is_active || chan->is_mapped) { 
+               DPRINTK ("EXIT\n");
+               return -EINVAL;
+       }
+
+       /* called outside SETFRAGMENT */
+       /* set defaults or do nothing */
+       if (val < 0) {
+
+               if (chan->frag_size && chan->frag_number)
+                       goto out;
+
+               DPRINTK ("\n");
+
+               chan->frag_size = (VIA_DEFAULT_FRAG_TIME * chan->rate *
+                                  ((chan->pcm_fmt & VIA_PCM_FMT_STEREO) ? 2 : 1) *
+                                  ((chan->pcm_fmt & VIA_PCM_FMT_16BIT) ? 2 : 1)) / 
+1000 - 1;
+
+               shift = 0;
+               while (chan->frag_size) {
+                       chan->frag_size >>= 1;
+                       shift++;
+               }
+               chan->frag_size = 1 << shift;
+
+               chan->frag_number = (VIA_DEFAULT_BUFFER_TIME / VIA_DEFAULT_FRAG_TIME);
+
+               DPRINTK ("setting default values %d %d\n", chan->frag_size, 
+chan->frag_number);
+       } else {
+               chan->frag_size = 1 << (val & 0xFFFF);
+               chan->frag_number = (val >> 16) & 0xFFFF;
+
+               DPRINTK ("using user values %d %d\n", chan->frag_size, 
+chan->frag_number);
+       }
+
+       /* quake3 wants frag_number to be a power of two */
+       shift = 0;
+       while (chan->frag_number) {
+               chan->frag_number >>= 1;
+               shift++;
+       }
+       chan->frag_number = 1 << shift;
+
+       if (chan->frag_size > VIA_MAX_FRAG_SIZE)
+               chan->frag_size = VIA_MAX_FRAG_SIZE;
+       else if (chan->frag_size < VIA_MIN_FRAG_SIZE)
+               chan->frag_size = VIA_MIN_FRAG_SIZE;
+
+       if (chan->frag_number < VIA_MIN_FRAG_NUMBER)
+                chan->frag_number = VIA_MIN_FRAG_NUMBER;
+
+       if ((chan->frag_number * chan->frag_size) / PAGE_SIZE > 
+VIA_MAX_BUFFER_DMA_PAGES)
+               chan->frag_number = (VIA_MAX_BUFFER_DMA_PAGES * PAGE_SIZE) / 
+chan->frag_size;
+
+out:
+       if (chan->is_record)
+               atomic_set (&chan->n_frags, 0);
+       else
+               atomic_set (&chan->n_frags, chan->frag_number);
+
+       DPRINTK ("EXIT\n");
+
+       return 0;
+}
 
 #ifdef VIA_CHAN_DUMP_BUFS
 /**
@@ -948,7 +1075,7 @@
 {
        int i;
 
-       for (i = 0; i < VIA_DMA_BUFFERS; i++) {
+       for (i = 0; i < chan->frag_number; i++) {
                DPRINTK ("#%02d: addr=%x, count=%u, flag=%d, eol=%d\n",
                         i, chan->sgtable[i].addr,
                         chan->sgtable[i].count & 0x00FFFFFF,
@@ -975,15 +1102,15 @@
 
        assert (chan->slop_len > 0);
 
-       if (chan->sw_ptr == (VIA_DMA_BUFFERS - 1))
+       if (chan->sw_ptr == (chan->frag_number - 1))
                chan->sw_ptr = 0;
        else
                chan->sw_ptr++;
 
        chan->slop_len = 0;
 
-       assert (atomic_read (&chan->n_bufs) > 0);
-       atomic_dec (&chan->n_bufs);
+       assert (atomic_read (&chan->n_frags) > 0);
+       atomic_dec (&chan->n_frags);
 
        DPRINTK ("EXIT\n");
 }
@@ -1003,7 +1130,7 @@
        if (!chan->is_active && chan->is_enabled) {
                chan->is_active = 1;
                sg_begin (chan);
-               DPRINTK("starting channel %s\n", chan->name);
+               DPRINTK ("starting channel %s\n", chan->name);
        }
 }
 
@@ -1213,7 +1340,7 @@
 {
        DPRINTK ("ENTER\n");
 
-       DPRINTK("EXIT, returning -ESPIPE\n");
+       DPRINTK ("EXIT, returning -ESPIPE\n");
        return -ESPIPE;
 }
 
@@ -1245,7 +1372,7 @@
                pci_read_config_byte (card->pdev, 0x43, &r43);
                pci_read_config_byte (card->pdev, 0x44, &r44);
                pci_read_config_byte (card->pdev, 0x48, &r48);
-               DPRINTK("PCI config: %02X %02X %02X %02X %02X %02X\n",
+               DPRINTK ("PCI config: %02X %02X %02X %02X %02X %02X\n",
                        r40,r41,r42,r43,r44,r48);
 
                spin_lock_irq (&card->lock);
@@ -1334,7 +1461,7 @@
        card->ac97.dev_mixer = register_sound_mixer (&via_mixer_fops, -1);
        if (card->ac97.dev_mixer < 0) {
                printk (KERN_ERR PFX "unable to register AC97 mixer, aborting\n");
-               DPRINTK("EXIT, returning -EIO\n");
+               DPRINTK ("EXIT, returning -EIO\n");
                return -EIO;
        }
 
@@ -1359,21 +1486,21 @@
 
 err_out:
        unregister_sound_mixer (card->ac97.dev_mixer);
-       DPRINTK("EXIT, returning %d\n", rc);
+       DPRINTK ("EXIT, returning %d\n", rc);
        return rc;
 }
 
 
 static void via_ac97_cleanup (struct via_info *card)
 {
-       DPRINTK("ENTER\n");
+       DPRINTK ("ENTER\n");
 
        assert (card != NULL);
        assert (card->ac97.dev_mixer >= 0);
 
        unregister_sound_mixer (card->ac97.dev_mixer);
 
-       DPRINTK("EXIT\n");
+       DPRINTK ("EXIT\n");
 }
 
 
@@ -1414,24 +1541,24 @@
 
        /* sanity check: make sure our h/w ptr doesn't have a weird value */
        assert (n >= 0);
-       assert (n < VIA_DMA_BUFFERS);
+       assert (n < chan->frag_number);
 
        /* reset SGD data structure in memory to reflect a full buffer,
         * and advance the h/w ptr, wrapping around to zero if needed
         */
-       if (n == (VIA_DMA_BUFFERS - 1)) {
-               chan->sgtable[n].count = (VIA_DMA_BUF_SIZE | VIA_EOL);
+       if (n == (chan->frag_number - 1)) {
+               chan->sgtable[n].count = (chan->frag_size | VIA_EOL);
                atomic_set (&chan->hw_ptr, 0);
        } else {
-               chan->sgtable[n].count = (VIA_DMA_BUF_SIZE | VIA_FLAG);
+               chan->sgtable[n].count = (chan->frag_size | VIA_FLAG);
                atomic_inc (&chan->hw_ptr);
        }
 
        /* accounting crap for SNDCTL_DSP_GETxPTR */
        chan->n_irqs++;
-       chan->bytes += VIA_DMA_BUF_SIZE;
+       chan->bytes += chan->frag_size;
        if (chan->bytes < 0) /* handle overflow of 31-bit value */
-               chan->bytes = VIA_DMA_BUF_SIZE;
+               chan->bytes = chan->frag_size;
 
        /* wake up anyone listening to see when interrupts occur */
        if (waitqueue_active (&chan->wait))
@@ -1445,25 +1572,25 @@
        if (chan->is_mapped)
                return;
 
-       /* If we are recording, then n_bufs represents the number
-        * of buffers waiting to be handled by userspace.
-        * If we are playback, then n_bufs represents the number
-        * of buffers remaining to be filled by userspace.
-        * We increment here.  If we reach max buffers (VIA_DMA_BUFFERS),
+       /* If we are recording, then n_frags represents the number
+        * of fragments waiting to be handled by userspace.
+        * If we are playback, then n_frags represents the number
+        * of fragments remaining to be filled by userspace.
+        * We increment here.  If we reach max number of fragments,
         * this indicates an underrun/overrun.  For this case under OSS,
         * we stop the record/playback process.
         */
-       if (atomic_read (&chan->n_bufs) < VIA_DMA_BUFFERS)
-               atomic_inc (&chan->n_bufs);
-       assert (atomic_read (&chan->n_bufs) <= VIA_DMA_BUFFERS);
+       if (atomic_read (&chan->n_frags) < chan->frag_number)
+               atomic_inc (&chan->n_frags);
+       assert (atomic_read (&chan->n_frags) <= chan->frag_number);
 
-       if (atomic_read (&chan->n_bufs) == VIA_DMA_BUFFERS) {
+       if (atomic_read (&chan->n_frags) == chan->frag_number) {
                chan->is_active = 0;
                via_chan_stop (chan->iobase);
        }
 
-       DPRINTK ("%s intr, channel n_bufs == %d\n", chan->name,
-                atomic_read (&chan->n_bufs));
+       DPRINTK ("%s intr, channel n_frags == %d\n", chan->name,
+                atomic_read (&chan->n_frags));
 }
 
 
@@ -1618,9 +1745,7 @@
        poll:           via_dsp_poll,
        llseek:         via_llseek,
        ioctl:          via_dsp_ioctl,
-#ifdef VIA_SUPPORT_MMAP
        mmap:           via_dsp_mmap,
-#endif
 };
 
 
@@ -1668,7 +1793,6 @@
 }
 
 
-#ifdef VIA_SUPPORT_MMAP
 static struct page * via_mm_nopage (struct vm_area_struct * vma,
                                    unsigned long address, int write_access)
 {
@@ -1685,8 +1809,6 @@
                 address,
                 write_access);
 
-       assert (VIA_DMA_BUF_SIZE == PAGE_SIZE);
-
         if (address > vma->vm_end) {
                DPRINTK ("EXIT, returning NOPAGE_SIGBUS\n");
                return NOPAGE_SIGBUS; /* Disallow mremap */
@@ -1702,7 +1824,7 @@
 
 #ifndef VIA_NDEBUG
        {
-       unsigned long max_bufs = VIA_DMA_BUFFERS;
+       unsigned long max_bufs = chan->frag_number;
        if (rd && wr) max_bufs *= 2;
        /* via_dsp_mmap() should ensure this */
        assert (pgoff < max_bufs);
@@ -1711,17 +1833,17 @@
 
        /* if full-duplex (read+write) and we have two sets of bufs,
         * then the playback buffers come first, sez soundcard.c */
-       if (pgoff >= VIA_DMA_BUFFERS) {
-               pgoff -= VIA_DMA_BUFFERS;
+       if (pgoff >= chan->page_number) {
+               pgoff -= chan->page_number;
                chan = &card->ch_in;
        } else if (!wr)
                chan = &card->ch_in;
 
-       assert ((((unsigned long)chan->sgbuf[pgoff].cpuaddr) % PAGE_SIZE) == 0);
+       assert ((((unsigned long)chan->pgtbl[pgoff].cpuaddr) % PAGE_SIZE) == 0);
 
-       dmapage = virt_to_page (chan->sgbuf[pgoff].cpuaddr);
+       dmapage = virt_to_page (chan->pgtbl[pgoff].cpuaddr);
        DPRINTK ("EXIT, returning page %p for cpuaddr %lXh\n",
-                dmapage, (unsigned long) chan->sgbuf[pgoff].cpuaddr);
+                dmapage, (unsigned long) chan->pgtbl[pgoff].cpuaddr);
        get_page (dmapage);
        return dmapage;
 }
@@ -1761,16 +1883,18 @@
                 vma->vm_end - vma->vm_start,
                 vma->vm_pgoff);
 
-       assert (VIA_DMA_BUF_SIZE == PAGE_SIZE);
-
        max_size = 0;
-       if (file->f_mode & FMODE_READ) {
+       if (vma->vm_flags & VM_READ) {
                rd = 1;
-               max_size += (VIA_DMA_BUFFERS * VIA_DMA_BUF_SIZE);
+               via_chan_set_buffering(card, &card->ch_in, -1);
+               via_chan_buffer_init (card, &card->ch_in);
+               max_size += card->ch_in.page_number << PAGE_SHIFT;
        }
-       if (file->f_mode & FMODE_WRITE) {
+       if (vma->vm_flags & VM_WRITE) {
                wr = 1;
-               max_size += (VIA_DMA_BUFFERS * VIA_DMA_BUF_SIZE);
+               via_chan_set_buffering(card, &card->ch_out, -1);
+               via_chan_buffer_init (card, &card->ch_out);
+               max_size += card->ch_out.page_number << PAGE_SHIFT;
        }
 
        start = vma->vm_start;
@@ -1802,10 +1926,9 @@
        rc = 0;
 
 out:
-       DPRINTK("EXIT, returning %d\n", rc);
+       DPRINTK ("EXIT, returning %d\n", rc);
        return rc;
 }
-#endif /* VIA_SUPPORT_MMAP */
 
 
 static ssize_t via_dsp_do_read (struct via_info *card,
@@ -1831,13 +1954,13 @@
         */
        n = chan->sw_ptr;
 
-       /* n_bufs represents the number of buffers waiting
+       /* n_frags represents the number of fragments waiting
         * to be copied to userland.  sleep until at least
         * one buffer has been read from the audio hardware.
         */
-       tmp = atomic_read (&chan->n_bufs);
+       tmp = atomic_read (&chan->n_frags);
        assert (tmp >= 0);
-       assert (tmp <= VIA_DMA_BUFFERS);
+       assert (tmp <= chan->frag_number);
        while (tmp == 0) {
                if (nonblock || !chan->is_active)
                        return -EAGAIN;
@@ -1848,18 +1971,18 @@
                if (signal_pending (current))
                        return -ERESTARTSYS;
 
-               tmp = atomic_read (&chan->n_bufs);
+               tmp = atomic_read (&chan->n_frags);
        }
 
        /* Now that we have a buffer we can read from, send
         * as much as sample data possible to userspace.
         */
-       while ((count > 0) && (chan->slop_len < VIA_DMA_BUF_SIZE)) {
-               size_t slop_left = VIA_DMA_BUF_SIZE - chan->slop_len;
+       while ((count > 0) && (chan->slop_len < chan->frag_size)) {
+               size_t slop_left = chan->frag_size - chan->slop_len;
 
                size = (count < slop_left) ? count : slop_left;
                if (copy_to_user (userbuf,
-                                 chan->sgbuf[n].cpuaddr + chan->slop_len,
+                                 chan->pgtbl[n / (PAGE_SIZE / 
+chan->frag_size)].cpuaddr + n % (PAGE_SIZE / chan->frag_size) + chan->slop_len,
                                  size))
                        return -EFAULT;
 
@@ -1871,7 +1994,7 @@
        /* If we didn't copy the buffer completely to userspace,
         * stop now.
         */
-       if (chan->slop_len < VIA_DMA_BUF_SIZE)
+       if (chan->slop_len < chan->frag_size)
                goto out;
 
        /*
@@ -1882,20 +2005,20 @@
        /* advance channel software pointer to point to
         * the next buffer from which we will copy
         */
-       if (chan->sw_ptr == (VIA_DMA_BUFFERS - 1))
+       if (chan->sw_ptr == (chan->frag_number - 1))
                chan->sw_ptr = 0;
        else
                chan->sw_ptr++;
 
        /* mark one less buffer waiting to be processed */
-       assert (atomic_read (&chan->n_bufs) > 0);
-       atomic_dec (&chan->n_bufs);
+       assert (atomic_read (&chan->n_frags) > 0);
+       atomic_dec (&chan->n_frags);
 
        /* we are at a block boundary, there is no fragment data */
        chan->slop_len = 0;
 
-       DPRINTK("Flushed block %u, sw_ptr now %u, n_bufs now %d\n",
-               n, chan->sw_ptr, atomic_read (&chan->n_bufs));
+       DPRINTK ("Flushed block %u, sw_ptr now %u, n_frags now %d\n",
+               n, chan->sw_ptr, atomic_read (&chan->n_frags));
 
        DPRINTK ("regs==%02X %02X %02X %08X %08X %08X %08X\n",
                 inb (card->baseaddr + 0x00),
@@ -1941,12 +2064,18 @@
                goto out_up;
        }
 
+       via_chan_set_buffering(card, &card->ch_in, -1);
+        rc = via_chan_buffer_init (card, &card->ch_in);
+
+       if (rc)
+               goto out_up;
+
        rc = via_dsp_do_read (card, buffer, count, nonblock);
 
 out_up:
        up (&card->syscall_sem);
 out:
-       DPRINTK("EXIT, returning %ld\n",(long) rc);
+       DPRINTK ("EXIT, returning %ld\n",(long) rc);
        return rc;
 }
 
@@ -1966,40 +2095,40 @@
        if (current->need_resched)
                schedule ();
 
-       /* grab current channel software pointer.  In the case of
-        * playback, this is pointing to the next buffer that
+       /* grab current channel fragment pointer.  In the case of
+        * playback, this is pointing to the next fragment that
         * should receive data from userland.
         */
        n = chan->sw_ptr;
 
-       /* n_bufs represents the number of buffers remaining
+       /* n_frags represents the number of fragments remaining
         * to be filled by userspace.  Sleep until
-        * at least one buffer is available for our use.
+        * at least one fragment is available for our use.
         */
-       tmp = atomic_read (&chan->n_bufs);
+       tmp = atomic_read (&chan->n_frags);
        assert (tmp >= 0);
-       assert (tmp <= VIA_DMA_BUFFERS);
+       assert (tmp <= chan->frag_number);
        while (tmp == 0) {
                if (nonblock || !chan->is_enabled)
                        return -EAGAIN;
 
-               DPRINTK ("Sleeping on block %d, tmp==%d, ir==%d\n", n, tmp, 
chan->is_record);
+               DPRINTK ("Sleeping on page %d, tmp==%d, ir==%d\n", n, tmp, 
+chan->is_record);
                interruptible_sleep_on (&chan->wait);
 
                if (signal_pending (current))
                        return -ERESTARTSYS;
 
-               tmp = atomic_read (&chan->n_bufs);
+               tmp = atomic_read (&chan->n_frags);
        }
 
-       /* Now that we have a buffer we can write to, fill it up
+       /* Now that we have at least one fragment we can write to, fill the buffer
         * as much as possible with data from userspace.
         */
-       while ((count > 0) && (chan->slop_len < VIA_DMA_BUF_SIZE)) {
-               size_t slop_left = VIA_DMA_BUF_SIZE - chan->slop_len;
+       while ((count > 0) && (chan->slop_len < chan->frag_size)) {
+               size_t slop_left = chan->frag_size - chan->slop_len;
 
                size = (count < slop_left) ? count : slop_left;
-               if (copy_from_user (chan->sgbuf[n].cpuaddr + chan->slop_len,
+               if (copy_from_user (chan->pgtbl[n / (PAGE_SIZE / 
+chan->frag_size)].cpuaddr + (n % (PAGE_SIZE / chan->frag_size)) * chan->frag_size + 
+chan->slop_len,
                                    userbuf, size))
                        return -EFAULT;
 
@@ -2009,36 +2138,36 @@
        }
 
        /* If we didn't fill up the buffer with data, stop now.
-        * Put a 'stop' marker in the DMA table too, to tell the
-        * audio hardware to stop if it gets here.
-        */
-       if (chan->slop_len < VIA_DMA_BUF_SIZE) {
+         * Put a 'stop' marker in the DMA table too, to tell the
+         * audio hardware to stop if it gets here.
+         */
+       if (chan->slop_len < chan->frag_size) {
                sgtable[n].count = cpu_to_le32 (chan->slop_len | VIA_EOL | VIA_STOP);
                goto out;
        }
 
        /*
-        * If we get to this point, we have filled a buffer with
-        * audio data, flush the buffer to audio hardware.
-        */
+         * If we get to this point, we have filled a buffer with
+         * audio data, flush the buffer to audio hardware.
+         */
 
        /* Record the true size for the audio hardware to notice */
-       if (n == (VIA_DMA_BUFFERS - 1))
-               sgtable[n].count = cpu_to_le32 (VIA_DMA_BUF_SIZE | VIA_EOL);
-       else
-               sgtable[n].count = cpu_to_le32 (VIA_DMA_BUF_SIZE | VIA_FLAG);
+        if (n == (chan->frag_number - 1))
+                sgtable[n].count = cpu_to_le32 (chan->frag_size | VIA_EOL);
+        else
+                sgtable[n].count = cpu_to_le32 (chan->frag_size | VIA_FLAG);
 
        /* advance channel software pointer to point to
         * the next buffer we will fill with data
         */
-       if (chan->sw_ptr == (VIA_DMA_BUFFERS - 1))
+       if (chan->sw_ptr == (chan->frag_number - 1))
                chan->sw_ptr = 0;
        else
                chan->sw_ptr++;
 
        /* mark one less buffer as being available for userspace consumption */
-       assert (atomic_read (&chan->n_bufs) > 0);
-       atomic_dec (&chan->n_bufs);
+       assert (atomic_read (&chan->n_frags) > 0);
+       atomic_dec (&chan->n_frags);
 
        /* we are at a block boundary, there is no fragment data */
        chan->slop_len = 0;
@@ -2046,8 +2175,8 @@
        /* if SGD has not yet been started, start it */
        via_chan_maybe_start (chan);
 
-       DPRINTK("Flushed block %u, sw_ptr now %u, n_bufs now %d\n",
-               n, chan->sw_ptr, atomic_read (&chan->n_bufs));
+       DPRINTK ("Flushed block %u, sw_ptr now %u, n_frags now %d\n",
+               n, chan->sw_ptr, atomic_read (&chan->n_frags));
 
        DPRINTK ("regs==%02X %02X %02X %08X %08X %08X %08X\n",
                 inb (card->baseaddr + 0x00),
@@ -2093,12 +2222,18 @@
                goto out_up;
        }
 
+       via_chan_set_buffering(card, &card->ch_out, -1);
+       rc = via_chan_buffer_init (card, &card->ch_out);
+
+       if (rc)
+               goto out_up;
+
        rc = via_dsp_do_write (card, buffer, count, nonblock);
 
 out_up:
        up (&card->syscall_sem);
 out:
-       DPRINTK("EXIT, returning %ld\n",(long) rc);
+       DPRINTK ("EXIT, returning %ld\n",(long) rc);
        return rc;
 }
 
@@ -2117,23 +2252,27 @@
        rd = (file->f_mode & FMODE_READ);
        wr = (file->f_mode & FMODE_WRITE);
 
-       if (wr && (atomic_read (&card->ch_out.n_bufs) == 0)) {
+       if (wr && (atomic_read (&card->ch_out.n_frags) == 0)) {
                assert (card->ch_out.is_active);
                 poll_wait(file, &card->ch_out.wait, wait);
        }
         if (rd) {
                /* XXX is it ok, spec-wise, to start DMA here? */
+               if (!card->ch_in.is_active) {
+                       via_chan_set_buffering(card, &card->ch_in, -1);
+                       via_chan_buffer_init(card, &card->ch_in);
+               }
                via_chan_maybe_start (&card->ch_in);
-               if (atomic_read (&card->ch_in.n_bufs) == 0)
+               if (atomic_read (&card->ch_in.n_frags) == 0)
                        poll_wait(file, &card->ch_in.wait, wait);
        }
 
-       if (wr && (atomic_read (&card->ch_out.n_bufs) > 0))
+       if (wr && ((atomic_read (&card->ch_out.n_frags) > 0) || 
+!card->ch_out.is_active))
                mask |= POLLOUT | POLLWRNORM;
-       if (rd && (atomic_read (&card->ch_in.n_bufs) > 0))
+       if (rd && (atomic_read (&card->ch_in.n_frags) > 0))
                mask |= POLLIN | POLLRDNORM;
 
-       DPRINTK("EXIT, returning %u\n", mask);
+       DPRINTK ("EXIT, returning %u\n", mask);
        return mask;
 }
 
@@ -2158,12 +2297,12 @@
        if (chan->slop_len > 0)
                via_chan_flush_frag (chan);
 
-       if (atomic_read (&chan->n_bufs) == VIA_DMA_BUFFERS)
+       if (atomic_read (&chan->n_frags) == chan->frag_number)
                goto out;
 
        via_chan_maybe_start (chan);
 
-       while (atomic_read (&chan->n_bufs) < VIA_DMA_BUFFERS) {
+       while (atomic_read (&chan->n_frags) < chan->frag_number) {
                if (nonblock) {
                        DPRINTK ("EXIT, returning -EAGAIN\n");
                        return -EAGAIN;
@@ -2178,7 +2317,7 @@
                pci_read_config_byte (card->pdev, 0x43, &r43);
                pci_read_config_byte (card->pdev, 0x44, &r44);
                pci_read_config_byte (card->pdev, 0x48, &r48);
-               DPRINTK("PCI config: %02X %02X %02X %02X %02X %02X\n",
+               DPRINTK ("PCI config: %02X %02X %02X %02X %02X %02X\n",
                        r40,r41,r42,r43,r44,r48);
 
                DPRINTK ("regs==%02X %02X %02X %08X %08X %08X %08X\n",
@@ -2195,7 +2334,7 @@
                        printk (KERN_ERR "sleeping but not active\n");
 #endif
 
-               DPRINTK ("sleeping, nbufs=%d\n", atomic_read (&chan->n_bufs));
+               DPRINTK ("sleeping, nbufs=%d\n", atomic_read (&chan->n_frags));
                interruptible_sleep_on (&chan->wait);
 
                if (signal_pending (current)) {
@@ -2213,7 +2352,7 @@
                pci_read_config_byte (card->pdev, 0x43, &r43);
                pci_read_config_byte (card->pdev, 0x44, &r44);
                pci_read_config_byte (card->pdev, 0x48, &r48);
-               DPRINTK("PCI config: %02X %02X %02X %02X %02X %02X\n",
+               DPRINTK ("PCI config: %02X %02X %02X %02X %02X %02X\n",
                        r40,r41,r42,r43,r44,r48);
 
                DPRINTK ("regs==%02X %02X %02X %08X %08X %08X %08X\n",
@@ -2225,7 +2364,7 @@
                         inl (card->baseaddr + 0x80),
                         inl (card->baseaddr + 0x84));
 
-               DPRINTK ("final nbufs=%d\n", atomic_read (&chan->n_bufs));
+               DPRINTK ("final nbufs=%d\n", atomic_read (&chan->n_frags));
        }
 #endif
 
@@ -2252,21 +2391,23 @@
 {
        audio_buf_info info;
 
-       info.fragstotal = VIA_DMA_BUFFERS;
-       info.fragsize = VIA_DMA_BUF_SIZE;
+       via_chan_set_buffering(card, chan, -1);
+
+       info.fragstotal = chan->frag_number;
+       info.fragsize = chan->frag_size;
 
        /* number of full fragments we can read/write without blocking */
-       info.fragments = atomic_read (&chan->n_bufs);
+       info.fragments = atomic_read (&chan->n_frags);
 
-       if ((chan->slop_len > 0) && (info.fragments > 0))
+       if ((chan->slop_len % chan->frag_size > 0) && (info.fragments > 0))
                info.fragments--;
 
        /* number of bytes that can be read or written immediately
         * without blocking.
         */
-       info.bytes = (info.fragments * VIA_DMA_BUF_SIZE);
-       if (chan->slop_len > 0)
-               info.bytes += VIA_DMA_BUF_SIZE - chan->slop_len;
+       info.bytes = (info.fragments * chan->frag_size);
+       if (chan->slop_len % chan->frag_size > 0)
+               info.bytes += chan->frag_size - (chan->slop_len % chan->frag_size);
 
        DPRINTK ("EXIT, returning fragstotal=%d, fragsize=%d, fragments=%d, 
bytes=%d\n",
                info.fragstotal,
@@ -2305,8 +2446,8 @@
 
        if (chan->is_active) {
                unsigned long extra;
-               info.ptr = atomic_read (&chan->hw_ptr) * VIA_DMA_BUF_SIZE;
-               extra = VIA_DMA_BUF_SIZE - inl (chan->iobase + 
VIA_BASE0_PCM_OUT_BLOCK_COUNT);
+               info.ptr = atomic_read (&chan->hw_ptr) * chan->frag_size;
+               extra = chan->frag_size - inl (chan->iobase + VIA_PCM_BLOCK_COUNT);
                info.ptr += extra;
                info.bytes += extra;
        } else {
@@ -2386,13 +2527,13 @@
 
        /* OSS API version.  XXX unverified */
        case OSS_GETVERSION:
-               DPRINTK("ioctl OSS_GETVERSION, EXIT, returning SOUND_VERSION\n");
+               DPRINTK ("ioctl OSS_GETVERSION, EXIT, returning SOUND_VERSION\n");
                rc = put_user (SOUND_VERSION, (int *)arg);
                break;
 
        /* list of supported PCM data formats */
        case SNDCTL_DSP_GETFMTS:
-               DPRINTK("DSP_GETFMTS, EXIT, returning AFMT U8|S16_LE\n");
+               DPRINTK ("DSP_GETFMTS, EXIT, returning AFMT U8|S16_LE\n");
                 rc = put_user (AFMT_U8 | AFMT_S16_LE, (int *)arg);
                break;
 
@@ -2402,20 +2543,19 @@
                        rc = -EFAULT;
                        break;
                }
-               DPRINTK("DSP_SETFMT, val==%d\n", val);
+               DPRINTK ("DSP_SETFMT, val==%d\n", val);
                if (val != AFMT_QUERY) {
                        rc = 0;
 
-                       if (rc == 0 && rd)
+                       if (rd)
                                rc = via_chan_set_fmt (card, &card->ch_in, val);
-                       if (rc == 0 && wr)
+
+                       if (rc >= 0 && wr)
                                rc = via_chan_set_fmt (card, &card->ch_out, val);
 
-                       if (rc <= 0) {
-                               if (rc == 0)
-                                       rc = -EINVAL;
+                       if (rc < 0)
                                break;
-                       }
+
                        val = rc;
                } else {
                        if ((rd && (card->ch_in.pcm_fmt & VIA_PCM_FMT_16BIT)) ||
@@ -2424,7 +2564,7 @@
                        else
                                val = AFMT_U8;
                }
-               DPRINTK("SETFMT EXIT, returning %d\n", val);
+               DPRINTK ("SETFMT EXIT, returning %d\n", val);
                 rc = put_user (val, (int *)arg);
                break;
 
@@ -2434,18 +2574,19 @@
                        rc = -EFAULT;
                        break;
                }
-               DPRINTK("DSP_CHANNELS, val==%d\n", val);
+               DPRINTK ("DSP_CHANNELS, val==%d\n", val);
                if (val != 0) {
                        rc = 0;
-                       if (rc == 0 && rd)
+
+                       if (rd)
                                rc = via_chan_set_stereo (card, &card->ch_in, val);
-                       if (rc == 0 && wr)
+
+                       if (rc >= 0 && wr)
                                rc = via_chan_set_stereo (card, &card->ch_out, val);
-                       if (rc <= 0) {
-                               if (rc == 0)
-                                       rc = -EINVAL;
+
+                       if (rc < 0)
                                break;
-                       }
+
                        val = rc;
                } else {
                        if ((rd && (card->ch_in.pcm_fmt & VIA_PCM_FMT_STEREO)) ||
@@ -2454,7 +2595,7 @@
                        else
                                val = 1;
                }
-               DPRINTK("CHANNELS EXIT, returning %d\n", val);
+               DPRINTK ("CHANNELS EXIT, returning %d\n", val);
                 rc = put_user (val, (int *)arg);
                break;
 
@@ -2464,21 +2605,21 @@
                        rc = -EFAULT;
                        break;
                }
-               DPRINTK("DSP_STEREO, val==%d\n", val);
+               DPRINTK ("DSP_STEREO, val==%d\n", val);
                rc = 0;
 
-               if (rc == 0 && rd)
+               if (rd)
                        rc = via_chan_set_stereo (card, &card->ch_in, val ? 2 : 1);
-               if (rc == 0 && wr)
+               if (rc >= 0 && wr)
                        rc = via_chan_set_stereo (card, &card->ch_out, val ? 2 : 1);
 
-               if (rc <= 0) {
-                       if (rc == 0)
-                               rc = -EINVAL;
+               if (rc < 0)
                        break;
-               }
-               DPRINTK("STEREO EXIT, returning %d\n", val);
-                rc = 0;
+
+               val = rc - 1;
+
+               DPRINTK ("STEREO EXIT, returning %d\n", val);
+               rc = put_user(val, (int *) arg);
                break;
 
        /* query or set sampling rate */
@@ -2487,7 +2628,7 @@
                        rc = -EFAULT;
                        break;
                }
-               DPRINTK("DSP_SPEED, val==%d\n", val);
+               DPRINTK ("DSP_SPEED, val==%d\n", val);
                if (val < 0) {
                        rc = -EINVAL;
                        break;
@@ -2495,16 +2636,14 @@
                if (val > 0) {
                        rc = 0;
 
-                       if (rc == 0 && rd)
+                       if (rd)
                                rc = via_chan_set_speed (card, &card->ch_in, val);
-                       if (rc == 0 && wr)
+                       if (rc >= 0 && wr)
                                rc = via_chan_set_speed (card, &card->ch_out, val);
 
-                       if (rc <= 0) {
-                               if (rc == 0)
-                                       rc = -EINVAL;
+                       if (rc < 0)
                                break;
-                       }
+
                        val = rc;
                } else {
                        if (rd)
@@ -2514,7 +2653,7 @@
                        else
                                val = 0;
                }
-               DPRINTK("SPEED EXIT, returning %d\n", val);
+               DPRINTK ("SPEED EXIT, returning %d\n", val);
                 rc = put_user (val, (int *)arg);
                break;
 
@@ -2522,7 +2661,7 @@
        case SNDCTL_DSP_SYNC:
                DPRINTK ("DSP_SYNC\n");
                if (wr) {
-                       DPRINTK("SYNC EXIT (after calling via_dsp_drain_playback)\n");
+                       DPRINTK ("SYNC EXIT (after calling 
+via_dsp_drain_playback)\n");
                        rc = via_dsp_drain_playback (card, &card->ch_out, nonblock);
                }
                break;
@@ -2531,12 +2670,19 @@
         case SNDCTL_DSP_RESET:
                DPRINTK ("DSP_RESET\n");
                if (rd) {
-                       via_chan_clear (&card->ch_in);
+                       via_chan_clear (card, &card->ch_in);
                        via_chan_pcm_fmt (&card->ch_in, 1);
+                       card->ch_in.frag_number = 0;
+                       card->ch_in.frag_size = 0;
+                       atomic_set(&card->ch_in.n_frags, 0);
                }
+
                if (wr) {
-                       via_chan_clear (&card->ch_out);
+                       via_chan_clear (card, &card->ch_out);
                        via_chan_pcm_fmt (&card->ch_out, 1);
+                       card->ch_out.frag_number = 0;
+                       card->ch_out.frag_size = 0;
+                       atomic_set(&card->ch_out.n_frags, 0);
                }
 
                rc = 0;
@@ -2544,40 +2690,47 @@
 
        /* obtain bitmask of device capabilities, such as mmap, full duplex, etc. */
        case SNDCTL_DSP_GETCAPS:
-               DPRINTK("DSP_GETCAPS\n");
+               DPRINTK ("DSP_GETCAPS\n");
                rc = put_user(VIA_DSP_CAP, (int *)arg);
                break;
 
-       /* obtain bitmask of device capabilities, such as mmap, full duplex, etc. */
+       /* obtain buffer fragment size */
        case SNDCTL_DSP_GETBLKSIZE:
-               DPRINTK("DSP_GETBLKSIZE\n");
-               rc = put_user(VIA_DMA_BUF_SIZE, (int *)arg);
+               DPRINTK ("DSP_GETBLKSIZE\n");
+
+               if (rd) {
+                       via_chan_set_buffering(card, &card->ch_in, -1);
+                       rc = put_user(card->ch_in.frag_size, (int *)arg);
+               } else if (wr) {
+                       via_chan_set_buffering(card, &card->ch_out, -1);
+                       rc = put_user(card->ch_out.frag_size, (int *)arg);
+               }
                break;
 
        /* obtain information about input buffering */
        case SNDCTL_DSP_GETISPACE:
-               DPRINTK("DSP_GETISPACE\n");
+               DPRINTK ("DSP_GETISPACE\n");
                if (rd)
                        rc = via_dsp_ioctl_space (card, &card->ch_in, (void*) arg);
                break;
 
        /* obtain information about output buffering */
        case SNDCTL_DSP_GETOSPACE:
-               DPRINTK("DSP_GETOSPACE\n");
+               DPRINTK ("DSP_GETOSPACE\n");
                if (wr)
                        rc = via_dsp_ioctl_space (card, &card->ch_out, (void*) arg);
                break;
 
        /* obtain information about input hardware pointer */
        case SNDCTL_DSP_GETIPTR:
-               DPRINTK("DSP_GETIPTR\n");
+               DPRINTK ("DSP_GETIPTR\n");
                if (rd)
                        rc = via_dsp_ioctl_ptr (card, &card->ch_in, (void*) arg);
                break;
 
        /* obtain information about output hardware pointer */
        case SNDCTL_DSP_GETOPTR:
-               DPRINTK("DSP_GETOPTR\n");
+               DPRINTK ("DSP_GETOPTR\n");
                if (wr)
                        rc = via_dsp_ioctl_ptr (card, &card->ch_out, (void*) arg);
                break;
@@ -2585,25 +2738,29 @@
        /* return number of bytes remaining to be played by DMA engine */
        case SNDCTL_DSP_GETODELAY:
                {
-               DPRINTK("DSP_GETODELAY\n");
+               DPRINTK ("DSP_GETODELAY\n");
 
                chan = &card->ch_out;
 
                if (!wr)
                        break;
 
-               val = VIA_DMA_BUFFERS - atomic_read (&chan->n_bufs);
+               if (chan->is_active) {
 
-               if (val > 0) {
-                       val *= VIA_DMA_BUF_SIZE;
-                       val -= VIA_DMA_BUF_SIZE -
-                              inl (chan->iobase + VIA_BASE0_PCM_OUT_BLOCK_COUNT);
-               }
-               val += chan->slop_len;
+                       val = chan->frag_number - atomic_read (&chan->n_frags);
 
-               assert (val <= (VIA_DMA_BUF_SIZE * VIA_DMA_BUFFERS));
+                       if (val > 0) {
+                               val *= chan->frag_size;
+                               val -= chan->frag_size -
+                                      inl (chan->iobase + VIA_PCM_BLOCK_COUNT);
+                       }
+                       val += chan->slop_len % chan->frag_size;
+               } else
+                       val = 0;
 
-               DPRINTK("GETODELAY EXIT, val = %d bytes\n", val);
+               assert (val <= (chan->frag_size * chan->frag_number));
+
+               DPRINTK ("GETODELAY EXIT, val = %d bytes\n", val);
                 rc = put_user (val, (int *)arg);
                break;
                }
@@ -2617,7 +2774,7 @@
                        rc = -EFAULT;
                        break;
                }
-               DPRINTK("DSP_SETTRIGGER, rd=%d, wr=%d, act=%d/%d, en=%d/%d\n",
+               DPRINTK ("DSP_SETTRIGGER, rd=%d, wr=%d, act=%d/%d, en=%d/%d\n",
                        rd, wr, card->ch_in.is_active, card->ch_out.is_active,
                        card->ch_in.is_enabled, card->ch_out.is_enabled);
 
@@ -2625,6 +2782,7 @@
 
                if (rd)
                        rc = via_dsp_ioctl_trigger (&card->ch_in, val);
+
                if (!rc && wr)
                        rc = via_dsp_ioctl_trigger (&card->ch_out, val);
 
@@ -2634,7 +2792,7 @@
         * with O_RDWR, this is mainly a no-op that always returns success.
         */
        case SNDCTL_DSP_SETDUPLEX:
-               DPRINTK("DSP_SETDUPLEX\n");
+               DPRINTK ("DSP_SETDUPLEX\n");
                if (!rd || !wr)
                        break;
                rc = 0;
@@ -2646,7 +2804,13 @@
                        rc = -EFAULT;
                        break;
                }
-               DPRINTK("DSP_SETFRAGMENT, val==%d\n", val);
+               DPRINTK ("DSP_SETFRAGMENT, val==%d\n", val);
+
+               if (rd)
+                       rc = via_chan_set_buffering(card, &card->ch_in, val);
+
+               if (wr)
+                       rc = via_chan_set_buffering(card, &card->ch_out, val);  
 
                DPRINTK ("SNDCTL_DSP_SETFRAGMENT (fragshift==0x%04X (%d), 
maxfrags==0x%04X (%d))\n",
                         val & 0xFFFF,
@@ -2654,13 +2818,12 @@
                         (val >> 16) & 0xFFFF,
                         (val >> 16) & 0xFFFF);
 
-               /* just to shut up some programs */
                rc = 0;
                break;
 
        /* inform device of an upcoming pause in input (or output). */
        case SNDCTL_DSP_POST:
-               DPRINTK("DSP_POST\n");
+               DPRINTK ("DSP_POST\n");
                if (wr) {
                        if (card->ch_out.slop_len > 0)
                                via_chan_flush_frag (&card->ch_out);
@@ -2678,15 +2841,14 @@
        }
 
        up (&card->syscall_sem);
-       DPRINTK("EXIT, returning %d\n", rc);
+       DPRINTK ("EXIT, returning %d\n", rc);
        return rc;
 }
 
 
 static int via_dsp_open (struct inode *inode, struct file *file)
 {
-       int rc, minor = MINOR(inode->i_rdev);
-       int got_read_chan = 0;
+       int minor = MINOR(inode->i_rdev);
        struct via_info *card;
        struct pci_dev *pdev;
        struct via_channel *chan;
@@ -2733,17 +2895,13 @@
        }
 
        file->private_data = card;
-       DPRINTK("file->f_mode == 0x%x\n", file->f_mode);
+       DPRINTK ("file->f_mode == 0x%x\n", file->f_mode);
 
        /* handle input from analog source */
        if (file->f_mode & FMODE_READ) {
                chan = &card->ch_in;
 
-               rc = via_chan_init (card, chan);
-               if (rc)
-                       goto err_out;
-
-               got_read_chan = 1;
+               via_chan_init (card, chan);
 
                /* why is this forced to 16-bit stereo in all drivers? */
                chan->pcm_fmt = VIA_PCM_FMT_16BIT | VIA_PCM_FMT_STEREO;
@@ -2756,30 +2914,28 @@
        if (file->f_mode & FMODE_WRITE) {
                chan = &card->ch_out;
 
-               rc = via_chan_init (card, chan);
-               if (rc)
-                       goto err_out_read_chan;
+               via_chan_init (card, chan);
 
-               if ((minor & 0xf) == SND_DEV_DSP16) {
-                       chan->pcm_fmt |= VIA_PCM_FMT_16BIT;
-                       via_set_rate (&card->ac97, chan, 44100);
+               if (file->f_mode & FMODE_READ) {
+                       /* if in duplex mode make the recording and playback channels
+                          have the same settings */
+                       chan->pcm_fmt = VIA_PCM_FMT_16BIT | VIA_PCM_FMT_STEREO;
+                       via_chan_pcm_fmt (chan, 0);
+                        via_set_rate (&card->ac97, chan, 44100);
                } else {
-                       via_set_rate (&card->ac97, chan, 8000);
+                        if ((minor & 0xf) == SND_DEV_DSP16) {
+                               chan->pcm_fmt = VIA_PCM_FMT_16BIT;
+                               via_chan_pcm_fmt (chan, 0);
+                               via_set_rate (&card->ac97, chan, 44100);
+                       } else {
+                               via_chan_pcm_fmt (chan, 0);
+                               via_set_rate (&card->ac97, chan, 8000);
+                       }
                }
-
-               via_chan_pcm_fmt (chan, 0);
        }
 
        DPRINTK ("EXIT, returning 0\n");
        return 0;
-
-err_out_read_chan:
-       if (got_read_chan)
-               via_chan_free (card, &card->ch_in);
-err_out:
-       up (&card->open_sem);
-       DPRINTK("ERROR EXIT, returning %d\n", rc);
-       return rc;
 }
 
 
@@ -2807,15 +2963,18 @@
                        printk (KERN_DEBUG "via_audio: ignoring drain playback error 
%d\n", rc);
 
                via_chan_free (card, &card->ch_out);
+               via_chan_buffer_free(card, &card->ch_out);
        }
 
-       if (file->f_mode & FMODE_READ)
+       if (file->f_mode & FMODE_READ) {
                via_chan_free (card, &card->ch_in);
+               via_chan_buffer_free (card, &card->ch_in);
+       }
 
        up (&card->syscall_sem);
        up (&card->open_sem);
 
-       DPRINTK("EXIT, returning 0\n");
+       DPRINTK ("EXIT, returning 0\n");
        return 0;
 }
 
@@ -2934,9 +3093,9 @@
                tmp &= 0xF0;
                tmp |= pdev->irq;
                pci_write_config_byte (pdev, 0x3C, tmp);
-               DPRINTK("new 0x3c==0x%02x\n", tmp);
+               DPRINTK ("new 0x3c==0x%02x\n", tmp);
        } else {
-               DPRINTK("IRQ reg 0x3c==0x%02x, irq==%d\n",
+               DPRINTK ("IRQ reg 0x3c==0x%02x, irq==%d\n",
                        tmp, tmp & 0x0F);
        }
 
@@ -3036,12 +3195,12 @@
 
 static void __exit cleanup_via82cxxx_audio(void)
 {
-       DPRINTK("ENTER\n");
+       DPRINTK ("ENTER\n");
 
        pci_unregister_driver (&via_driver);
        via_cleanup_proc ();
 
-       DPRINTK("EXIT\n");
+       DPRINTK ("EXIT\n");
 }
 
 
@@ -3133,7 +3292,7 @@
 
                );
 
-       DPRINTK("EXIT, returning %d\n", len);
+       DPRINTK ("EXIT, returning %d\n", len);
        return len;
 
 #undef YN

Reply via email to