Update of /cvsroot/alsa/alsa-kernel/pci
In directory sc8-pr-cvs1:/tmp/cvs-serv6129

Modified Files:
        via82xx.c 
Log Message:
- support DSX channels on VIA8233, 8233C and 8235.
  this supports up to 4 multi-playbacks.
- support the secondary capture on the chips above.
- experimental SPDIF support.
- clean up the code, separate low-level initialization routines for
  each chipset.



Index: via82xx.c
===================================================================
RCS file: /cvsroot/alsa/alsa-kernel/pci/via82xx.c,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -r1.13 -r1.14
--- via82xx.c   23 Dec 2002 10:34:20 -0000      1.13
+++ via82xx.c   10 Jan 2003 17:47:57 -0000      1.14
@@ -23,6 +23,26 @@
  *
  */
 
+/*
+ * Changes:
+ *
+ * Dec. 19, 2002       Takashi Iwai <[EMAIL PROTECTED]>
+ *     - use the DSX channels for the first pcm playback.
+ *       (on VIA8233, 8233C and 8235 only)
+ *       this will allow you play simultaneously up to 4 streams.
+ *       multi-channel playback is assigned to the second device
+ *       on these chips.
+ *     - support the secondary capture (on VIA8233/C,8235)
+ *     - SPDIF support
+ *       the DSX3 channel can be used for SPDIF output.
+ *       on VIA8233A, this channel is assigned to the second pcm
+ *       playback.
+ *       the card config of alsa-lib will assign the correct
+ *       device for applications.
+ *     - clean up the code, separate low-level initialization
+ *       routines for each chipset.
+ */
+
 #include <sound/driver.h>
 #include <asm/io.h>
 #include <linux/delay.h>
@@ -48,7 +68,7 @@
 MODULE_DESCRIPTION("VIA VT82xx audio");
 MODULE_LICENSE("GPL");
 MODULE_CLASSES("{sound}");
-MODULE_DEVICES("{{VIA,VT82C686A/B/C,pci},{VIA,VT8233A/B/C}}");
+MODULE_DEVICES("{{VIA,VT82C686A/B/C,pci},{VIA,VT8233A/C,8235}}");
 
 static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;     /* Index 0-MAX */
 static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;      /* ID for this card */
@@ -184,6 +204,13 @@
 #define VIA_TBL_BIT_EOL                0x80000000
 
 /*
+ */
+
+typedef struct _snd_via82xx via82xx_t;
+typedef struct via_dev viadev_t;
+#define chip_t via82xx_t
+
+/*
  * pcm stream
  */
 
@@ -194,18 +221,20 @@
 
 #define VIA_TABLE_SIZE 255
 
-typedef struct {
-       unsigned long reg_offset;
+struct via_dev {
+       unsigned int reg_offset;
+       int direction;  /* playback = 0, capture = 1 */
         snd_pcm_substream_t *substream;
        int running;
        unsigned int tbl_entries; /* # descriptors */
        u32 *table; /* physical address + flag */
        dma_addr_t table_addr;
        struct snd_via_sg_table *idx_table;
+       /* for recovery from the unexpected pointer */
        unsigned int lastpos;
        unsigned int bufsize;
        unsigned int bufsize2;
-} viadev_t;
+};
 
 
 /*
@@ -218,7 +247,7 @@
                           unsigned int periods, unsigned int fragsize)
 {
        unsigned int i, idx, ofs, rest;
-       struct snd_sg_buf *sgbuf = snd_magic_cast(snd_pcm_sgbuf_t, 
substream->dma_private, return -EINVAL);
+       struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream);
 
        if (! dev->table) {
                /* the start of each lists must be aligned to 8 bytes,
@@ -297,8 +326,13 @@
 
 enum { TYPE_VIA686 = 1, TYPE_VIA8233 };
 
-typedef struct _snd_via82xx via82xx_t;
-#define chip_t via82xx_t
+#define VIA_MAX_DEVS   7       /* 4 playback, 1 multi, 2 capture */
+
+struct via_rate_lock {
+       spinlock_t lock;
+       int rate;
+       int used;
+};
 
 struct _snd_via82xx {
        int irq;
@@ -314,9 +348,10 @@
        struct pci_dev *pci;
        snd_card_t *card;
 
-       snd_pcm_t *pcm;
-       viadev_t playback;
-       viadev_t capture;
+       int num_devs;
+       int playback_devno, multi_devno, capture_devno;
+       viadev_t devs[VIA_MAX_DEVS];
+       struct via_rate_lock rates[2]; /* playback and capture */
 
        snd_rawmidi_t *rmidi;
 
@@ -361,7 +396,7 @@
                if (!((val = snd_via82xx_codec_xread(chip)) & VIA_REG_AC97_BUSY))
                        return val & 0xffff;
        }
-       snd_printk("codec_ready: codec %i is not ready [0x%x]\n", secondary, 
snd_via82xx_codec_xread(chip));
+       snd_printk(KERN_ERR "codec_ready: codec %i is not ready [0x%x]\n", secondary, 
+snd_via82xx_codec_xread(chip));
        return -EIO;
 }
  
@@ -377,7 +412,7 @@
                if ((val = snd_via82xx_codec_xread(chip)) & stat)
                        return val & 0xffff;
        }
-       snd_printk("codec_valid: codec %i is not valid [0x%x]\n", secondary, 
snd_via82xx_codec_xread(chip));
+       snd_printk(KERN_ERR "codec_valid: codec %i is not valid [0x%x]\n", secondary, 
+snd_via82xx_codec_xread(chip));
        return -EIO;
 }
  
@@ -452,11 +487,57 @@
        viadev->lastpos = 0;
 }
 
-static int snd_via82xx_trigger(via82xx_t *chip, viadev_t *viadev, int cmd)
+
+/*
+ *  Interrupt handler
+ */
+
+static void snd_via82xx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
 {
+       via82xx_t *chip = snd_magic_cast(via82xx_t, dev_id, return);
+       unsigned int status;
+       int i;
+
+       spin_lock(&chip->reg_lock);
+       if (chip->chip_type == TYPE_VIA686) {
+               /* check mpu401 interrupt */
+               status = inl(VIAREG(chip, SGD_SHADOW));
+               if ((status & 0x00000077) == 0) {
+                       spin_unlock(&chip->reg_lock);
+                       if (chip->rmidi != NULL)
+                               snd_mpu401_uart_interrupt(irq, 
+chip->rmidi->private_data, regs);
+                       return;
+               }
+       }
+       /* check status for each stream */
+       for (i = 0; i < chip->num_devs; i++) {
+               viadev_t *viadev = &chip->devs[i];
+               if (inb(chip->port + viadev->reg_offset) & 
+(VIA_REG_STAT_EOL|VIA_REG_STAT_FLAG)) {
+                       outb(VIA_REG_STAT_FLAG | VIA_REG_STAT_EOL, VIAREG(chip, 
+OFFSET_STATUS) + viadev->reg_offset);
+                       if (viadev->substream && viadev->running) {
+                               spin_unlock(&chip->reg_lock);
+                               snd_pcm_period_elapsed(viadev->substream);
+                               spin_lock(&chip->reg_lock);
+                       }
+               }
+       }
+       spin_unlock(&chip->reg_lock);
+}
+
+/*
+ *  PCM callbacks
+ */
+
+/*
+ * trigger callback
+ */
+static int snd_via82xx_pcm_trigger(snd_pcm_substream_t * substream, int cmd)
+{
+       via82xx_t *chip = snd_pcm_substream_chip(substream);
+       viadev_t *viadev = (viadev_t *)substream->runtime->private_data;
        unsigned char val;
        unsigned long port = chip->port + viadev->reg_offset;
-       
+
        if (chip->chip_type == TYPE_VIA8233)
                val = VIA_REG_CTRL_INT;
        else
@@ -487,114 +568,104 @@
 }
 
 
-static int snd_via82xx_set_format(via82xx_t *chip, viadev_t *viadev,
-                                 snd_pcm_substream_t *substream)
+/*
+ * pointer callbacks
+ */
+
+/*
+ * calculate the linear position at the given sg-buffer index and the rest count
+ */
+static inline unsigned int calc_linear_pos(viadev_t *viadev, unsigned int idx, 
+unsigned int count)
 {
-       snd_pcm_runtime_t *runtime = substream->runtime;
-       unsigned long port = chip->port + viadev->reg_offset;
+       unsigned int size, res;
 
-       snd_via82xx_channel_reset(chip, viadev);
+       size = viadev->idx_table[idx].size;
+       res = viadev->idx_table[idx].offset + size - count;
 
-       outl((u32)viadev->table_addr, port + VIA_REG_OFFSET_TABLE_PTR);
-       switch (chip->chip_type) {
-       case TYPE_VIA686:
-               outb(VIA_REG_TYPE_AUTOSTART |
-                    (runtime->format == SNDRV_PCM_FORMAT_S16_LE ? VIA_REG_TYPE_16BIT 
: 0) |
-                    (runtime->channels > 1 ? VIA_REG_TYPE_STEREO : 0) |
-                    ((viadev->reg_offset & 0x10) == 0 ? VIA_REG_TYPE_INT_LSAMPLE : 0) 
|
-                    VIA_REG_TYPE_INT_EOL |
-                    VIA_REG_TYPE_INT_FLAG, port + VIA_REG_OFFSET_TYPE);
-               break;
-       case TYPE_VIA8233:
-               if (viadev->reg_offset == VIA_REG_MULTPLAY_STATUS) {
-                       unsigned int slots;
-                       int fmt = (runtime->format == SNDRV_PCM_FORMAT_S16_LE) ? 
VIA_REG_MULTPLAY_FMT_16BIT : VIA_REG_MULTPLAY_FMT_8BIT;
-                       fmt |= runtime->channels << 4;
-                       outb(fmt, port + VIA_REG_OFFSET_TYPE);
-                       /* set sample number to slot 3, 4, 7, 8, 6, 9 */
-                       switch (runtime->channels) {
-                       case 1: slots = (1<<0) | (1<<4); break;
-                       case 2: slots = (1<<0) | (2<<4); break;
-                       case 4: slots = (1<<0) | (2<<4) | (3<<8) | (4<<12); break;
-                       case 6: slots = (1<<0) | (2<<4) | (5<<8) | (6<<12) | (3<<16) | 
(4<<20); break;
-                       default: slots = 0; break;
-                       }
-                       /* STOP index is never reached */
-                       outl(0xff000000 | slots, port + VIA_REG_OFFSET_STOP_IDX);
+       /* check the validity of the calculated position */
+       if (size < count || (res < viadev->lastpos && (res >= viadev->bufsize2 || 
+viadev->lastpos < viadev->bufsize2))) {
+#ifdef POINTER_DEBUG
+               printk("fail: idx = %i/%i, lastpos = 0x%x, bufsize2 = 0x%x, offsize = 
+0x%x, size = 0x%x, count = 0x%x\n", idx, viadev->tbl_entries, viadev->lastpos, 
+viadev->bufsize2, viadev->idx_table[idx].offset, viadev->idx_table[idx].size, count);
+#endif
+               /* count register returns full size when end of buffer is reached */
+               if (size != count) {
+                       snd_printd(KERN_ERR "invalid via82xx_cur_ptr, using last valid 
+pointer\n");
+                       res = viadev->lastpos;
                } else {
-                       outl((runtime->format == SNDRV_PCM_FORMAT_S16_LE ? 
VIA8233_REG_TYPE_16BIT : 0) |
-                            (runtime->channels > 1 ? VIA8233_REG_TYPE_STEREO : 0) |
-                            0xff000000,    /* STOP index is never reached */
-                            port + VIA_REG_OFFSET_STOP_IDX);
+                       res = viadev->idx_table[idx].offset + size;
+                       if (res < viadev->lastpos && (res >= viadev->bufsize2 || 
+viadev->lastpos < viadev->bufsize2)) {
+                               snd_printd(KERN_ERR "invalid via82xx_cur_ptr (2), 
+using last valid pointer\n");
+                               res = viadev->lastpos;
+                       }
                }
-               break;
        }
-       return 0;
+       viadev->lastpos = res; /* remember the last positiion */
+       if (res >= viadev->bufsize)
+               res -= viadev->bufsize;
+       return res;
 }
 
 /*
- *  Interrupt handler
+ * get the current pointer on via686
  */
-
-static inline void snd_via82xx_update(via82xx_t *chip, viadev_t *viadev)
+static snd_pcm_uframes_t snd_via686_pcm_pointer(snd_pcm_substream_t *substream)
 {
-       outb(VIA_REG_STAT_FLAG | VIA_REG_STAT_EOL, VIAREG(chip, OFFSET_STATUS) + 
viadev->reg_offset);
-       if (viadev->substream && viadev->running) {
-               spin_unlock(&chip->reg_lock);
-               snd_pcm_period_elapsed(viadev->substream);
-               spin_lock(&chip->reg_lock);
-       }
-}
+       via82xx_t *chip = snd_pcm_substream_chip(substream);
+       viadev_t *viadev = (viadev_t *)substream->runtime->private_data;
+       unsigned int idx, ptr, count, res;
 
-static void snd_via82xx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
-{
-       via82xx_t *chip = snd_magic_cast(via82xx_t, dev_id, return);
-       unsigned int status;
+       snd_assert(viadev->tbl_entries, return 0);
+       if (!(inb(VIAREG(chip, OFFSET_STATUS) + viadev->reg_offset) & 
+VIA_REG_STAT_ACTIVE))
+               return 0;
 
        spin_lock(&chip->reg_lock);
-       if (chip->chip_type == TYPE_VIA686) {
-               /* check mpu401 interrupt */
-               status = inl(VIAREG(chip, SGD_SHADOW));
-               if ((status & 0x00000077) == 0) {
-                       spin_unlock(&chip->reg_lock);
-                       if (chip->rmidi != NULL)
-                               snd_mpu401_uart_interrupt(irq, 
chip->rmidi->private_data, regs);
-                       return;
-               }
-       }
-       /* check status for each stream */
-       if (inb(chip->port + chip->playback.reg_offset) & 
(VIA_REG_STAT_EOL|VIA_REG_STAT_FLAG))
-               snd_via82xx_update(chip, &chip->playback);
-       if (inb(chip->port + chip->capture.reg_offset) & 
(VIA_REG_STAT_EOL|VIA_REG_STAT_FLAG))
-               snd_via82xx_update(chip, &chip->capture);
+       count = inl(VIAREG(chip, OFFSET_CURR_COUNT) + viadev->reg_offset) & 0xffffff;
+       /* The via686a does not have the current index register,
+        * so we need to calculate the index from CURR_PTR.
+        */
+       ptr = inl(VIAREG(chip, OFFSET_CURR_PTR) + viadev->reg_offset);
+       if (ptr <= (unsigned int)viadev->table_addr)
+               idx = 0;
+       else /* CURR_PTR holds the address + 8 */
+               idx = ((ptr - (unsigned int)viadev->table_addr) / 8 - 1) % 
+viadev->tbl_entries;
+       res = calc_linear_pos(viadev, idx, count);
        spin_unlock(&chip->reg_lock);
+
+       return bytes_to_frames(substream->runtime, res);
 }
 
 /*
- *  PCM part
+ * get the current pointer on via823x
  */
-
-static int snd_via82xx_playback_trigger(snd_pcm_substream_t * substream,
-                                       int cmd)
+static snd_pcm_uframes_t snd_via8233_pcm_pointer(snd_pcm_substream_t *substream)
 {
        via82xx_t *chip = snd_pcm_substream_chip(substream);
+       viadev_t *viadev = (viadev_t *)substream->runtime->private_data;
+       unsigned int idx, count, res;
+       
+       snd_assert(viadev->tbl_entries, return 0);
+       if (!(inb(VIAREG(chip, OFFSET_STATUS) + viadev->reg_offset) & 
+VIA_REG_STAT_ACTIVE))
+               return 0;
+       spin_lock(&chip->reg_lock);
+       count = inl(VIAREG(chip, OFFSET_CURR_COUNT) + viadev->reg_offset);
+       idx = count >> 24;
+       count &= 0xffffff;
+       res = calc_linear_pos(viadev, idx, count);
+       spin_unlock(&chip->reg_lock);
 
-       return snd_via82xx_trigger(chip, &chip->playback, cmd);
+       return bytes_to_frames(substream->runtime, res);
 }
 
-static int snd_via82xx_capture_trigger(snd_pcm_substream_t * substream,
-                                      int cmd)
-{
-       via82xx_t *chip = snd_pcm_substream_chip(substream);
-
-       return snd_via82xx_trigger(chip, &chip->capture, cmd);
-}
 
+/*
+ * hw_params callback:
+ * allocate the buffer and build up the buffer description table
+ */
 static int snd_via82xx_hw_params(snd_pcm_substream_t * substream,
                                 snd_pcm_hw_params_t * hw_params)
 {
        via82xx_t *chip = snd_pcm_substream_chip(substream);
-       viadev_t *viadev = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 
&chip->playback : &chip->capture;
+       viadev_t *viadev = (viadev_t *)substream->runtime->private_data;
        int err;
 
        err = snd_pcm_sgbuf_alloc(substream, params_buffer_bytes(hw_params));
@@ -605,131 +676,184 @@
                              params_period_bytes(hw_params));
        if (err < 0)
                return err;
-       return err;
+
+       return 0;
 }
 
+/*
+ * hw_free callback:
+ * clean up the buffer description table and release the buffer
+ */
 static int snd_via82xx_hw_free(snd_pcm_substream_t * substream)
 {
        via82xx_t *chip = snd_pcm_substream_chip(substream);
-       viadev_t *viadev = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 
&chip->playback : &chip->capture;
+       viadev_t *viadev = (viadev_t *)substream->runtime->private_data;
 
        clean_via_table(viadev, substream, chip->pci);
        snd_pcm_sgbuf_free(substream);
        return 0;
 }
 
-static int snd_via82xx_playback_prepare(snd_pcm_substream_t * substream)
+
+/*
+ * prepare callback for playback and capture on via686
+ */
+static void via686_setup_format(via82xx_t *chip, viadev_t *viadev, snd_pcm_runtime_t 
+*runtime)
+{
+       unsigned long port = chip->port + viadev->reg_offset;
+
+       snd_via82xx_channel_reset(chip, viadev);
+       /* this must be set after channel_reset */
+       outl((u32)viadev->table_addr, port + VIA_REG_OFFSET_TABLE_PTR);
+       outb(VIA_REG_TYPE_AUTOSTART |
+            (runtime->format == SNDRV_PCM_FORMAT_S16_LE ? VIA_REG_TYPE_16BIT : 0) |
+            (runtime->channels > 1 ? VIA_REG_TYPE_STEREO : 0) |
+            ((viadev->reg_offset & 0x10) == 0 ? VIA_REG_TYPE_INT_LSAMPLE : 0) |
+            VIA_REG_TYPE_INT_EOL |
+            VIA_REG_TYPE_INT_FLAG, port + VIA_REG_OFFSET_TYPE);
+}
+
+static int snd_via686_playback_prepare(snd_pcm_substream_t *substream)
 {
        via82xx_t *chip = snd_pcm_substream_chip(substream);
+       viadev_t *viadev = (viadev_t *)substream->runtime->private_data;
        snd_pcm_runtime_t *runtime = substream->runtime;
 
        snd_ac97_set_rate(chip->ac97, AC97_PCM_FRONT_DAC_RATE, runtime->rate);
-       snd_ac97_set_rate(chip->ac97, AC97_PCM_SURR_DAC_RATE, runtime->rate);
-       snd_ac97_set_rate(chip->ac97, AC97_PCM_LFE_DAC_RATE, runtime->rate);
-       snd_ac97_set_rate(chip->ac97, AC97_SPDIF, runtime->rate);
-       if (chip->chip_type == TYPE_VIA8233 &&
-           chip->playback.reg_offset != VIA_REG_MULTPLAY_STATUS) {
-               unsigned int tmp;
-               /* I don't understand this stuff but its from the documentation and 
this way it works */
-               outb(0 , VIAREG(chip, PLAYBACK_VOLUME_L));
-               outb(0 , VIAREG(chip, PLAYBACK_VOLUME_R));
-               tmp = inl(VIAREG(chip, PLAYBACK_STOP_IDX)) & ~0xfffff;
-               outl(tmp | (0xffff * runtime->rate)/(48000/16), VIAREG(chip, 
PLAYBACK_STOP_IDX));
-       }
-       return snd_via82xx_set_format(chip, &chip->playback, substream);
+       via686_setup_format(chip, viadev, runtime);
+       return 0;
 }
 
-static int snd_via82xx_capture_prepare(snd_pcm_substream_t * substream)
+static int snd_via686_capture_prepare(snd_pcm_substream_t *substream)
 {
        via82xx_t *chip = snd_pcm_substream_chip(substream);
+       viadev_t *viadev = (viadev_t *)substream->runtime->private_data;
        snd_pcm_runtime_t *runtime = substream->runtime;
 
        snd_ac97_set_rate(chip->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate);
-       if (chip->chip_type == TYPE_VIA8233)
-               outb(VIA_REG_CAPTURE_FIFO_ENABLE, VIAREG(chip, CAPTURE_FIFO));
-       return snd_via82xx_set_format(chip, &chip->capture, substream);
+       via686_setup_format(chip, viadev, runtime);
+       return 0;
 }
 
-static inline unsigned int snd_via82xx_cur_ptr(via82xx_t *chip, viadev_t *viadev)
+/*
+ * lock the current rate
+ */
+static int via_lock_rate(struct via_rate_lock *rec, int rate)
 {
-       unsigned int val, ptr, count, res;
-
-       snd_assert(viadev->tbl_entries, return 0);
-       if (!(inb(VIAREG(chip, OFFSET_STATUS) + viadev->reg_offset) & 
VIA_REG_STAT_ACTIVE))
-               return 0;
-
-       spin_lock(&chip->reg_lock);
-
-       switch (chip->chip_type) {
-       case TYPE_VIA686:
-               count &= 0xffffff;
-               /* The via686a does not have the current index register,
-                * so we need to calculate the index from CURR_PTR.
-                */
-               ptr = inl(VIAREG(chip, OFFSET_CURR_PTR) + viadev->reg_offset);
-               count = inl(VIAREG(chip, OFFSET_CURR_COUNT) + viadev->reg_offset) & 
0xffffff;
-               if (ptr <= (unsigned int)viadev->table_addr)
-                       val = 0;
-               else /* CURR_PTR holds the address + 8 */
-                       val = ((ptr - (unsigned int)viadev->table_addr) / 8 - 1) % 
viadev->tbl_entries;
-               break;
-
-       case TYPE_VIA8233:
-       default:
-               count = inl(VIAREG(chip, OFFSET_CURR_COUNT) + viadev->reg_offset);
-               val = count >> 24;
-               count &= 0xffffff;
-               break;
-       }
-
-       /* convert to the linear position */
-       ptr = viadev->idx_table[val].size;
-       res = viadev->idx_table[val].offset + ptr - count;
-
-       if (ptr < count || (res < viadev->lastpos && (res >= viadev->bufsize2 || 
viadev->lastpos < viadev->bufsize2))) {
-#ifdef POINTER_DEBUG
-               printk("fail: val = %i/%i, lastpos = 0x%x, bufsize2 = 0x%x, offsize = 
0x%x, size = 0x%x, count = 0x%x\n", val, viadev->tbl_entries, viadev->lastpos, 
viadev->bufsize2, viadev->idx_table[val].offset, viadev->idx_table[val].size, count);
-#endif
-               /* VIA8233 count register returns full size when end of buffer is 
reached */
-               if (ptr != count) {
-                       snd_printk("invalid via82xx_cur_ptr, using last valid 
pointer\n");
-                       res = viadev->lastpos;
-               } else {
-                       res = viadev->idx_table[val].offset + ptr;
-                       if (res < viadev->lastpos && (res >= viadev->bufsize2 || 
viadev->lastpos < viadev->bufsize2)) {
-                               snd_printk("invalid via82xx_cur_ptr (2), using last 
valid pointer\n");
-                               res = viadev->lastpos;
-                       }
+       spin_lock(&rec->lock);
+       if (rec->rate) {
+               if (rec->rate != rate && rec->used > 1) {
+                       spin_unlock(&rec->lock);
+                       return -EINVAL;
                }
-       }
+       } else
+               rec->rate = rate;
+       spin_unlock(&rec->lock);
+       return 0;
+}
 
-       viadev->lastpos = res;
-       spin_unlock(&chip->reg_lock);
+/*
+ * prepare callback for DSX playback on via823x
+ */
+static int snd_via8233_playback_prepare(snd_pcm_substream_t *substream)
+{
+       via82xx_t *chip = snd_pcm_substream_chip(substream);
+       viadev_t *viadev = (viadev_t *)substream->runtime->private_data;
+       unsigned long port = chip->port + viadev->reg_offset;
+       snd_pcm_runtime_t *runtime = substream->runtime;
 
-       return res;
+       if (via_lock_rate(&chip->rates[0], runtime->rate) < 0)
+               return -EINVAL;
+       snd_ac97_set_rate(chip->ac97, AC97_PCM_FRONT_DAC_RATE, runtime->rate);
+       if (viadev->reg_offset == 0x30) /* DSX3 */
+               snd_ac97_set_rate(chip->ac97, AC97_SPDIF, runtime->rate);
+       snd_via82xx_channel_reset(chip, viadev);
+       outl((u32)viadev->table_addr, port + VIA_REG_OFFSET_TABLE_PTR);
+       outb(0 , VIAREG(chip, PLAYBACK_VOLUME_L));
+       outb(0 , VIAREG(chip, PLAYBACK_VOLUME_R));
+       outl((runtime->format == SNDRV_PCM_FORMAT_S16_LE ? VIA8233_REG_TYPE_16BIT : 0) 
+| /* format */
+            (runtime->channels > 1 ? VIA8233_REG_TYPE_STEREO : 0) | /* stereo */
+            (0xffff * runtime->rate)/(48000/16) | /* rate */
+            0xff000000,    /* STOP index is never reached */
+            port + VIA_REG_OFFSET_STOP_IDX);
+       return 0;
 }
 
-static snd_pcm_uframes_t snd_via82xx_playback_pointer(snd_pcm_substream_t * substream)
+/*
+ * prepare callback for multi-channel playback on via823x
+ */
+static int snd_via8233_multi_prepare(snd_pcm_substream_t *substream)
 {
        via82xx_t *chip = snd_pcm_substream_chip(substream);
-       return bytes_to_frames(substream->runtime, snd_via82xx_cur_ptr(chip, 
&chip->playback));
+       viadev_t *viadev = (viadev_t *)substream->runtime->private_data;
+       unsigned long port = chip->port + viadev->reg_offset;
+       snd_pcm_runtime_t *runtime = substream->runtime;
+       unsigned int slots;
+       int fmt;
+
+       if (via_lock_rate(&chip->rates[0], runtime->rate) < 0)
+               return -EINVAL;
+       snd_ac97_set_rate(chip->ac97, AC97_PCM_FRONT_DAC_RATE, runtime->rate);
+       snd_ac97_set_rate(chip->ac97, AC97_PCM_SURR_DAC_RATE, runtime->rate);
+       snd_ac97_set_rate(chip->ac97, AC97_PCM_LFE_DAC_RATE, runtime->rate);
+       snd_via82xx_channel_reset(chip, viadev);
+       outl((u32)viadev->table_addr, port + VIA_REG_OFFSET_TABLE_PTR);
+
+       fmt = (runtime->format == SNDRV_PCM_FORMAT_S16_LE) ? 
+VIA_REG_MULTPLAY_FMT_16BIT : VIA_REG_MULTPLAY_FMT_8BIT;
+       fmt |= runtime->channels << 4;
+       outb(fmt, port + VIA_REG_OFFSET_TYPE);
+       /* set sample number to slot 3, 4, 7, 8, 6, 9 */
+       /* corresponding to FL, FR, RL, RR, C, LFE ?? */
+       switch (runtime->channels) {
+       case 1: slots = (1<<0) | (1<<4); break;
+       case 2: slots = (1<<0) | (2<<4); break;
+       case 3: slots = (1<<0) | (2<<4) | (5<<8); break;
+       case 4: slots = (1<<0) | (2<<4) | (3<<8) | (4<<12); break;
+       case 5: slots = (1<<0) | (2<<4) | (5<<8) | (3<<12) | (4<<16); break;
+       case 6: slots = (1<<0) | (2<<4) | (5<<8) | (6<<12) | (3<<16) | (4<<20); break;
+       default: slots = 0; break;
+       }
+       /* STOP index is never reached */
+       outl(0xff000000 | slots, port + VIA_REG_OFFSET_STOP_IDX);
+       return 0;
 }
 
-static snd_pcm_uframes_t snd_via82xx_capture_pointer(snd_pcm_substream_t * substream)
+/*
+ * prepare callback for capture on via823x
+ */
+static int snd_via8233_capture_prepare(snd_pcm_substream_t *substream)
 {
        via82xx_t *chip = snd_pcm_substream_chip(substream);
-       return bytes_to_frames(substream->runtime, snd_via82xx_cur_ptr(chip, 
&chip->capture));
+       viadev_t *viadev = (viadev_t *)substream->runtime->private_data;
+       unsigned long port = chip->port + viadev->reg_offset;
+       snd_pcm_runtime_t *runtime = substream->runtime;
+
+       if (via_lock_rate(&chip->rates[1], runtime->rate) < 0)
+               return -EINVAL;
+       snd_ac97_set_rate(chip->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate);
+       snd_via82xx_channel_reset(chip, viadev);
+       outl((u32)viadev->table_addr, port + VIA_REG_OFFSET_TABLE_PTR);
+       outb(VIA_REG_CAPTURE_FIFO_ENABLE, VIAREG(chip, CAPTURE_FIFO));
+       outl((runtime->format == SNDRV_PCM_FORMAT_S16_LE ? VIA8233_REG_TYPE_16BIT : 0) 
+|
+            (runtime->channels > 1 ? VIA8233_REG_TYPE_STEREO : 0) |
+            0xff000000,    /* STOP index is never reached */
+            port + VIA_REG_OFFSET_STOP_IDX);
+       return 0;
 }
 
-static snd_pcm_hardware_t snd_via82xx_playback =
+
+/*
+ * pcm hardware definition, identical for both playback and capture
+ */
+static snd_pcm_hardware_t snd_via82xx_hw =
 {
        .info =                 (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
                                 SNDRV_PCM_INFO_BLOCK_TRANSFER |
                                 SNDRV_PCM_INFO_MMAP_VALID |
                                 SNDRV_PCM_INFO_PAUSE),
        .formats =              SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
-       .rates =                0,
-       .rate_min =             8000,
+       .rates =                SNDRV_PCM_RATE_48000,
+       .rate_min =             48000,
        .rate_max =             48000,
        .channels_min =         1,
        .channels_max =         2,
@@ -741,151 +865,310 @@
        .fifo_size =            0,
 };
 
-static snd_pcm_hardware_t snd_via82xx_capture =
-{
-       .info =                 (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
-                                SNDRV_PCM_INFO_BLOCK_TRANSFER |
-                                SNDRV_PCM_INFO_MMAP_VALID),
-       .formats =              SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
-       .rates =                0,
-       .rate_min =             8000,
-       .rate_max =             48000,
-       .channels_min =         1,
-       .channels_max =         2,
-       .buffer_bytes_max =     128 * 1024,
-       .period_bytes_min =     32,
-       .period_bytes_max =     128 * 1024,
-       .periods_min =          2,
-       .periods_max =          VIA_TABLE_SIZE / 2,
-       .fifo_size =            0,
-};
-
-static unsigned int channels[] = {
-       1, 2, 4, 6
-};
-
-#define CHANNELS sizeof(channels) / sizeof(channels[0])
 
-static snd_pcm_hw_constraint_list_t hw_constraints_channels = {
-       .count = CHANNELS,
-       .list = channels,
-       .mask = 0,
-};
-
-static int snd_via82xx_playback_open(snd_pcm_substream_t * substream)
+/*
+ * open callback skeleton
+ */
+static int snd_via82xx_pcm_open(via82xx_t *chip, viadev_t *viadev, 
+snd_pcm_substream_t * substream)
 {
-       via82xx_t *chip = snd_pcm_substream_chip(substream);
        snd_pcm_runtime_t *runtime = substream->runtime;
        int err;
+       unsigned long flags;
+       struct via_rate_lock *ratep;
+
+       runtime->hw = snd_via82xx_hw;
+       
+       /* set the hw rate condition */
+       ratep = &chip->rates[viadev->direction];
+       spin_lock_irqsave(&ratep->lock, flags);
+       ratep->used++;
+       if (! ratep->rate) {
+               int idx = viadev->direction ? AC97_RATES_ADC : AC97_RATES_FRONT_DAC;
+               runtime->hw.rates = chip->ac97->rates[idx];
+               if (runtime->hw.rates & SNDRV_PCM_RATE_8000)
+                       runtime->hw.rate_min = 8000;
+       } else {
+               /* a fixed rate */
+               runtime->hw.rates = SNDRV_PCM_RATE_KNOT;
+               runtime->hw.rate_max = runtime->hw.rate_min = ratep->rate;
+       }
+       spin_unlock_irqrestore(&ratep->lock, flags);
 
-       chip->playback.substream = substream;
-       runtime->hw = snd_via82xx_playback;
-       runtime->hw.rates = chip->ac97->rates[AC97_RATES_FRONT_DAC];
-       if (!(runtime->hw.rates & SNDRV_PCM_RATE_8000))
-               runtime->hw.rate_min = 48000;
        if ((err = snd_pcm_sgbuf_init(substream, chip->pci, 32)) < 0)
                return err;
        /* we may remove following constaint when we modify table entries
           in interrupt */
        if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) 
< 0)
                return err;
-       if (chip->chip_type == TYPE_VIA8233) {
-               runtime->hw.channels_max = 6;
-               snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 
&hw_constraints_channels);
-       }
+
+       runtime->private_data = viadev;
+       viadev->substream = substream;
+
        return 0;
 }
 
-static int snd_via82xx_capture_open(snd_pcm_substream_t * substream)
+
+/*
+ * open callback for playback on via686 and via823x DSX
+ */
+static int snd_via82xx_playback_open(snd_pcm_substream_t * substream)
 {
        via82xx_t *chip = snd_pcm_substream_chip(substream);
-       snd_pcm_runtime_t *runtime = substream->runtime;
+       viadev_t *viadev = &chip->devs[chip->playback_devno + substream->number];
+
+       return snd_via82xx_pcm_open(chip, viadev, substream);
+}
+
+/*
+ * open callback for playback on via823x multi-channel
+ */
+static int snd_via8233_multi_open(snd_pcm_substream_t * substream)
+{
+       via82xx_t *chip = snd_pcm_substream_chip(substream);
+       viadev_t *viadev = &chip->devs[chip->multi_devno];
        int err;
+       /* channels constraint for VIA8233A
+        * 3 and 5 channels are not supported
+        */
+       static unsigned int channels[] = {
+               1, 2, 4, 6
+       };
+       static snd_pcm_hw_constraint_list_t hw_constraints_channels = {
+               .count = ARRAY_SIZE(channels),
+               .list = channels,
+               .mask = 0,
+       };
 
-       chip->capture.substream = substream;
-       runtime->hw = snd_via82xx_capture;
-       runtime->hw.rates = chip->ac97->rates[AC97_RATES_ADC];
-       if (!(runtime->hw.rates & SNDRV_PCM_RATE_8000))
-               runtime->hw.rate_min = 48000;
-       if ((err = snd_pcm_sgbuf_init(substream, chip->pci, 32)) < 0)
-               return err;
-       if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) 
< 0)
+       if ((err = snd_via82xx_pcm_open(chip, viadev, substream)) < 0)
                return err;
+       substream->runtime->hw.channels_max = 6;
+       if (chip->revision == VIA_REV_8233A)
+               snd_pcm_hw_constraint_list(substream->runtime, 0, 
+SNDRV_PCM_HW_PARAM_CHANNELS, &hw_constraints_channels);
        return 0;
 }
 
-static int snd_via82xx_playback_close(snd_pcm_substream_t * substream)
+/*
+ * open callback for capture on via686 and via823x
+ */
+static int snd_via82xx_capture_open(snd_pcm_substream_t * substream)
 {
        via82xx_t *chip = snd_pcm_substream_chip(substream);
-       chip->playback.substream = NULL;
-       snd_pcm_sgbuf_delete(substream);
-       return 0;
+       viadev_t *viadev = &chip->devs[chip->capture_devno + substream->pcm->device];
+
+       return snd_via82xx_pcm_open(chip, viadev, substream);
 }
 
-static int snd_via82xx_capture_close(snd_pcm_substream_t * substream)
+/*
+ * close callback
+ */
+static int snd_via82xx_pcm_close(snd_pcm_substream_t * substream)
 {
        via82xx_t *chip = snd_pcm_substream_chip(substream);
-       chip->capture.substream = NULL;
+       viadev_t *viadev = (viadev_t *)substream->runtime->private_data;
+       unsigned long flags;
+       struct via_rate_lock *ratep;
+
+       /* release the rate lock */
+       ratep = &chip->rates[viadev->direction];
+       spin_lock_irqsave(&ratep->lock, flags);
+       ratep->used--;
+       if (! ratep->used)
+               ratep->rate = 0;
+       spin_unlock_irqrestore(&ratep->lock, flags);
+
+       viadev->substream = NULL;
        snd_pcm_sgbuf_delete(substream);
        return 0;
 }
 
-static snd_pcm_ops_t snd_via82xx_playback_ops = {
+
+/* via686 playback callbacks */
+static snd_pcm_ops_t snd_via686_playback_ops = {
        .open =         snd_via82xx_playback_open,
-       .close =        snd_via82xx_playback_close,
+       .close =        snd_via82xx_pcm_close,
        .ioctl =        snd_pcm_lib_ioctl,
        .hw_params =    snd_via82xx_hw_params,
        .hw_free =      snd_via82xx_hw_free,
-       .prepare =      snd_via82xx_playback_prepare,
-       .trigger =      snd_via82xx_playback_trigger,
-       .pointer =      snd_via82xx_playback_pointer,
+       .prepare =      snd_via686_playback_prepare,
+       .trigger =      snd_via82xx_pcm_trigger,
+       .pointer =      snd_via686_pcm_pointer,
        .copy =         snd_pcm_sgbuf_ops_copy_playback,
        .silence =      snd_pcm_sgbuf_ops_silence,
        .page =         snd_pcm_sgbuf_ops_page,
 };
 
-static snd_pcm_ops_t snd_via82xx_capture_ops = {
+/* via686 capture callbacks */
+static snd_pcm_ops_t snd_via686_capture_ops = {
        .open =         snd_via82xx_capture_open,
-       .close =        snd_via82xx_capture_close,
+       .close =        snd_via82xx_pcm_close,
        .ioctl =        snd_pcm_lib_ioctl,
        .hw_params =    snd_via82xx_hw_params,
        .hw_free =      snd_via82xx_hw_free,
-       .prepare =      snd_via82xx_capture_prepare,
-       .trigger =      snd_via82xx_capture_trigger,
-       .pointer =      snd_via82xx_capture_pointer,
+       .prepare =      snd_via686_capture_prepare,
+       .trigger =      snd_via82xx_pcm_trigger,
+       .pointer =      snd_via686_pcm_pointer,
        .copy =         snd_pcm_sgbuf_ops_copy_capture,
        .silence =      snd_pcm_sgbuf_ops_silence,
        .page =         snd_pcm_sgbuf_ops_page,
 };
 
-static void snd_via82xx_pcm_free(snd_pcm_t *pcm)
+/* via823x DSX playback callbacks */
+static snd_pcm_ops_t snd_via8233_playback_ops = {
+       .open =         snd_via82xx_playback_open,
+       .close =        snd_via82xx_pcm_close,
+       .ioctl =        snd_pcm_lib_ioctl,
+       .hw_params =    snd_via82xx_hw_params,
+       .hw_free =      snd_via82xx_hw_free,
+       .prepare =      snd_via8233_playback_prepare,
+       .trigger =      snd_via82xx_pcm_trigger,
+       .pointer =      snd_via8233_pcm_pointer,
+       .copy =         snd_pcm_sgbuf_ops_copy_playback,
+       .silence =      snd_pcm_sgbuf_ops_silence,
+       .page =         snd_pcm_sgbuf_ops_page,
+};
+
+/* via823x multi-channel playback callbacks */
+static snd_pcm_ops_t snd_via8233_multi_ops = {
+       .open =         snd_via8233_multi_open,
+       .close =        snd_via82xx_pcm_close,
+       .ioctl =        snd_pcm_lib_ioctl,
+       .hw_params =    snd_via82xx_hw_params,
+       .hw_free =      snd_via82xx_hw_free,
+       .prepare =      snd_via8233_multi_prepare,
+       .trigger =      snd_via82xx_pcm_trigger,
+       .pointer =      snd_via8233_pcm_pointer,
+       .copy =         snd_pcm_sgbuf_ops_copy_playback,
+       .silence =      snd_pcm_sgbuf_ops_silence,
+       .page =         snd_pcm_sgbuf_ops_page,
+};
+
+/* via823x capture callbacks */
+static snd_pcm_ops_t snd_via8233_capture_ops = {
+       .open =         snd_via82xx_capture_open,
+       .close =        snd_via82xx_pcm_close,
+       .ioctl =        snd_pcm_lib_ioctl,
+       .hw_params =    snd_via82xx_hw_params,
+       .hw_free =      snd_via82xx_hw_free,
+       .prepare =      snd_via8233_capture_prepare,
+       .trigger =      snd_via82xx_pcm_trigger,
+       .pointer =      snd_via8233_pcm_pointer,
+       .copy =         snd_pcm_sgbuf_ops_copy_capture,
+       .silence =      snd_pcm_sgbuf_ops_silence,
+       .page =         snd_pcm_sgbuf_ops_page,
+};
+
+
+/*
+ * create pcm instances for VIA8233, 8233C and 8235 (not 8233A)
+ */
+static int __devinit snd_via8233_pcm_new(via82xx_t *chip)
 {
-       via82xx_t *chip = snd_magic_cast(via82xx_t, pcm->private_data, return);
-       chip->pcm = NULL;
+       snd_pcm_t *pcm;
+       int i, err;
+
+       chip->playback_devno = 0;       /* x 4 */
+       chip->multi_devno = 4;          /* x 1 */
+       chip->capture_devno = 5;        /* x 2 */
+       chip->num_devs = 7;
+
+       /* PCM #0:  4 DSX playbacks and 1 capture */
+       err = snd_pcm_new(chip->card, chip->card->shortname, 0, 4, 1, &pcm);
+       if (err < 0)
+               return err;
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via8233_playback_ops);
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via8233_capture_ops);
+       pcm->private_data = chip;
+       strcpy(pcm->name, chip->card->shortname);
+       /* set up playbacks */
+       for (i = 0; i < 4; i++) {
+               chip->devs[i].reg_offset = 0x10 * i;
+               chip->devs[i].direction = 0;
+       }
+       /* capture */
+       chip->devs[chip->capture_devno].reg_offset = VIA_REG_CAPTURE_8233_STATUS;
+       chip->devs[chip->capture_devno].direction = 1;
+
+       /* PCM #1:  multi-channel playback and 2nd capture */
+       err = snd_pcm_new(chip->card, chip->card->shortname, 1, 1, 1, &pcm);
+       if (err < 0)
+               return err;
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via8233_multi_ops);
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via8233_capture_ops);
+       pcm->private_data = chip;
+       strcpy(pcm->name, chip->card->shortname);
+       /* set up playback */
+       chip->devs[chip->multi_devno].reg_offset = VIA_REG_MULTPLAY_STATUS;
+       chip->devs[chip->multi_devno].direction = 0;
+       /* set up capture */
+       chip->devs[chip->capture_devno + 1].reg_offset = VIA_REG_CAPTURE_8233_STATUS + 
+0x10;
+       chip->devs[chip->capture_devno + 1].direction = 1;
+       return 0;
 }
 
-static int __devinit snd_via82xx_pcm(via82xx_t *chip, int device, snd_pcm_t ** rpcm)
+/*
+ * create pcm instances for VIA8233A
+ */
+static int __devinit snd_via8233a_pcm_new(via82xx_t *chip)
 {
        snd_pcm_t *pcm;
        int err;
 
-       if (rpcm)
-               *rpcm = NULL;
-       err = snd_pcm_new(chip->card, chip->card->shortname, device, 1, 1, &pcm);
+       chip->playback_devno = 0;
+       chip->multi_devno = 1;
+       chip->capture_devno = 2;
+       chip->num_devs = 3;
+
+       /* PCM #0:  multi-channel playback and capture */
+       err = snd_pcm_new(chip->card, chip->card->shortname, 0, 1, 1, &pcm);
        if (err < 0)
                return err;
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via8233_multi_ops);
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via8233_capture_ops);
+       pcm->private_data = chip;
+       strcpy(pcm->name, chip->card->shortname);
+       /* set up playback */
+       chip->devs[chip->multi_devno].reg_offset = VIA_REG_MULTPLAY_STATUS;
+       chip->devs[chip->multi_devno].direction = 0;
+       /* capture */
+       chip->devs[chip->capture_devno].reg_offset = VIA_REG_CAPTURE_8233_STATUS;
+       chip->devs[chip->capture_devno].direction = 1;
 
-       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via82xx_playback_ops);
-       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via82xx_capture_ops);
-
+       /* PCM #1:  DXS3 playback (for spdif) */
+       err = snd_pcm_new(chip->card, chip->card->shortname, 1, 1, 0, &pcm);
+       if (err < 0)
+               return err;
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via8233_playback_ops);
        pcm->private_data = chip;
-       pcm->private_free = snd_via82xx_pcm_free;
-       pcm->info_flags = 0;
        strcpy(pcm->name, chip->card->shortname);
-       chip->pcm = pcm;
+       /* set up playback */
+       chip->devs[chip->playback_devno].reg_offset = 0x30;
+       chip->devs[chip->playback_devno].direction = 0;
+       return 0;
+}
 
-       if (rpcm)
-               *rpcm = NULL;
+/*
+ * create a pcm instance for via686a/b
+ */
+static int __devinit snd_via686_pcm_new(via82xx_t *chip)
+{
+       snd_pcm_t *pcm;
+       int err;
+
+       chip->playback_devno = 0;
+       chip->capture_devno = 1;
+       chip->num_devs = 2;
+
+       err = snd_pcm_new(chip->card, chip->card->shortname, 0, 1, 1, &pcm);
+       if (err < 0)
+               return err;
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via686_playback_ops);
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via686_capture_ops);
+       pcm->private_data = chip;
+       strcpy(pcm->name, chip->card->shortname);
+       chip->devs[0].reg_offset = VIA_REG_PLAYBACK_STATUS;
+       chip->devs[0].direction = 0;
+       chip->devs[1].reg_offset = VIA_REG_CAPTURE_STATUS;
+       chip->devs[1].direction = 1;
        return 0;
 }
 
@@ -911,23 +1194,25 @@
 static int snd_via8233_capture_source_get(snd_kcontrol_t *kcontrol, 
snd_ctl_elem_value_t *ucontrol)
 {
        via82xx_t *chip = snd_kcontrol_chip(kcontrol);
-       ucontrol->value.enumerated.item[0] = inb(VIAREG(chip, CAPTURE_CHANNEL)) & 
VIA_REG_CAPTURE_CHANNEL_MIC ? 1 : 0;
+       unsigned long port = chip->port + kcontrol->id.index ? 
+(VIA_REG_CAPTURE_CHANNEL + 0x10) : VIA_REG_CAPTURE_CHANNEL;
+       ucontrol->value.enumerated.item[0] = inb(port) & VIA_REG_CAPTURE_CHANNEL_MIC ? 
+1 : 0;
        return 0;
 }
 
 static int snd_via8233_capture_source_put(snd_kcontrol_t *kcontrol, 
snd_ctl_elem_value_t *ucontrol)
 {
        via82xx_t *chip = snd_kcontrol_chip(kcontrol);
+       unsigned long port = chip->port + kcontrol->id.index ? 
+(VIA_REG_CAPTURE_CHANNEL + 0x10) : VIA_REG_CAPTURE_CHANNEL;
        unsigned long flags;
        u8 val, oval;
 
        spin_lock_irqsave(&chip->reg_lock, flags);
-       oval = inb(VIAREG(chip, CAPTURE_CHANNEL));
+       oval = inb(port);
        val = oval & ~VIA_REG_CAPTURE_CHANNEL_MIC;
        if (ucontrol->value.enumerated.item[0])
                val |= VIA_REG_CAPTURE_CHANNEL_MIC;
        if (val != oval)
-               outb(val, VIAREG(chip, CAPTURE_CHANNEL));
+               outb(val, port);
        spin_unlock_irqrestore(&chip->reg_lock, flags);
        return val != oval;
 }
@@ -940,13 +1225,59 @@
        .put = snd_via8233_capture_source_put,
 };
 
+static int snd_via8233_dxs3_spdif_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t 
+*uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+       uinfo->count = 1;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = 1;
+       return 0;
+}
+
+static int snd_via8233_dxs3_spdif_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t 
+*ucontrol)
+{
+       via82xx_t *chip = snd_kcontrol_chip(kcontrol);
+       u8 val;
+
+       pci_read_config_byte(chip->pci, 0x49, &val);
+       ucontrol->value.integer.value[0] = (val & 0x08) ? 1 : 0;
+       return 0;
+}
+
+static int snd_via8233_dxs3_spdif_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t 
+*ucontrol)
+{
+       via82xx_t *chip = snd_kcontrol_chip(kcontrol);
+       u8 val, oval;
+
+       pci_read_config_byte(chip->pci, 0x49, &oval);
+       val = oval & ~0x08;
+       if (ucontrol->value.integer.value[0])
+               val |= 0x08;
+       if (val != oval) {
+               pci_write_config_byte(chip->pci, 0x49, val);
+               return 1;
+       }
+       return 0;
+}
+
+static snd_kcontrol_new_t snd_via8233_dxs3_spdif_control __devinitdata = {
+       .name = "IEC958 Output Switch",
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .info = snd_via8233_dxs3_spdif_info,
+       .get = snd_via8233_dxs3_spdif_get,
+       .put = snd_via8233_dxs3_spdif_put,
+};
+
+/*
+ */
+
 static void snd_via82xx_mixer_free_ac97(ac97_t *ac97)
 {
        via82xx_t *chip = snd_magic_cast(via82xx_t, ac97->private_data, return);
        chip->ac97 = NULL;
 }
 
-static int __devinit snd_via82xx_mixer(via82xx_t *chip)
+static int __devinit snd_via82xx_mixer_new(via82xx_t *chip)
 {
        ac97_t ac97;
        int err;
@@ -1014,6 +1345,100 @@
  *
  */
 
+static int snd_via8233_init_misc(via82xx_t *chip, int dev)
+{
+       int i, err, caps;
+
+       caps = chip->revision == VIA_REV_8233A ? 1 : 2;
+       for (i = 0; i < caps; i++) {
+               snd_via8233_capture_source.index = i;
+               err = snd_ctl_add(chip->card, 
+snd_ctl_new1(&snd_via8233_capture_source, chip));
+               if (err < 0)
+                       return err;
+       }
+       err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_via8233_dxs3_spdif_control, 
+chip));
+       if (err < 0)
+               return err;
+       return 0;
+}
+
+static int snd_via686_init_misc(via82xx_t *chip, int dev)
+{
+       unsigned char legacy, legacy_cfg;
+       int rev_h = 0;
+
+       legacy = chip->old_legacy;
+       legacy_cfg = chip->old_legacy_cfg;
+       legacy |= 0x40;         /* disable MIDI */
+       legacy &= ~0x08;        /* disable joystick */
+       if (chip->revision >= 0x20) {
+               if (check_region(pci_resource_start(chip->pci, 2), 4)) {
+                       rev_h = 0;
+                       legacy &= ~0x80;        /* disable PCI I/O 2 */
+               } else {
+                       rev_h = 1;
+                       legacy |= 0x80;         /* enable PCI I/O 2 */
+               }
+       }
+       pci_write_config_byte(chip->pci, 0x42, legacy);
+       pci_write_config_byte(chip->pci, 0x43, legacy_cfg);
+       if (rev_h && mpu_port[dev] >= 0x200) {  /* force MIDI */
+               legacy |= 0x02; /* enable MPU */
+               pci_write_config_dword(chip->pci, 0x18, (mpu_port[dev] & 0xfffc) | 
+0x01);
+       } else {
+               if (rev_h && (legacy & 0x02)) {
+                       mpu_port[dev] = pci_resource_start(chip->pci, 2);
+                       if (mpu_port[dev] < 0x200)      /* bad value */
+                               legacy &= ~0x02;        /* disable MIDI */
+               } else {
+                       switch (mpu_port[dev]) {        /* force MIDI */
+                       case 0x300:
+                       case 0x310:
+                       case 0x320:
+                       case 0x330:
+                               legacy_cfg &= ~(3 << 2);
+                               legacy_cfg |= (mpu_port[dev] & 0x0030) >> 2;
+                               legacy |= 0x02;
+                               break;
+                       default:                        /* no, use BIOS settings */
+                               if (legacy & 0x02)
+                                       mpu_port[dev] = 0x300 + ((legacy_cfg & 0x000c) 
+<< 2);
+                       }
+               }
+       }
+       pci_write_config_byte(chip->pci, 0x42, legacy);
+       pci_write_config_byte(chip->pci, 0x43, legacy_cfg);
+       if (legacy & 0x02) {
+               if (check_region(mpu_port[dev], 2)) {
+                       printk(KERN_WARNING "unable to get MPU-401 port at 0x%lx, 
+skipping\n", mpu_port[dev]);
+                       legacy &= ~0x02;
+                       pci_write_config_byte(chip->pci, 0x42, legacy);
+                       goto __skip_mpu;
+               }
+               if (snd_mpu401_uart_new(chip->card, 0, MPU401_HW_VIA686A,
+                                       mpu_port[dev], 0,
+                                       chip->irq, 0,
+                                       &chip->rmidi) < 0) {
+                       printk(KERN_WARNING "unable to initialize MPU-401 at 0x%lx, 
+skipping\n", mpu_port[dev]);
+                       legacy &= ~0x02;
+                       pci_write_config_byte(chip->pci, 0x42, legacy);
+                       goto __skip_mpu;
+               }
+               legacy &= ~0x40;        /* enable MIDI interrupt */
+               pci_write_config_byte(chip->pci, 0x42, legacy);
+       __skip_mpu:
+               ;
+       }
+       
+       /* card switches */
+       return snd_ctl_add(chip->card, snd_ctl_new1(&snd_via82xx_joystick_control, 
+chip));
+}
+
+
+/*
+ *
+ */
+
 static int __devinit snd_via82xx_chip_init(via82xx_t *chip)
 {
        ac97_t ac97;
@@ -1102,20 +1527,18 @@
                outl(0, chip->port + 0x8c);
        }
 
-       /* disable interrupts */
-       snd_via82xx_channel_reset(chip, &chip->playback);
-       snd_via82xx_channel_reset(chip, &chip->capture);
        return 0;
 }
 
 static int snd_via82xx_free(via82xx_t *chip)
 {
+       int i;
+
        if (chip->irq < 0)
                goto __end_hw;
        /* disable interrupts */
-       snd_via82xx_channel_reset(chip, &chip->playback);
-       snd_via82xx_channel_reset(chip, &chip->capture);
-       /* --- */
+       for (i = 0; i < chip->num_devs; i++)
+               snd_via82xx_channel_reset(chip, &chip->devs[i]);
        synchronize_irq(chip->irq);
       __end_hw:
        if (chip->res_port) {
@@ -1160,6 +1583,7 @@
 
        spin_lock_init(&chip->reg_lock);
        spin_lock_init(&chip->ac97_lock);
+       spin_lock_init(&chip->rate_lock);
        chip->card = card;
        chip->pci = pci;
        chip->irq = -1;
@@ -1185,21 +1609,6 @@
        pci_read_config_byte(pci, PCI_REVISION_ID, &chip->revision);
        synchronize_irq(chip->irq);
 
-       /* initialize offsets */
-       switch (chip->chip_type) {
-       case TYPE_VIA686:
-               chip->playback.reg_offset = VIA_REG_PLAYBACK_STATUS;
-               chip->capture.reg_offset = VIA_REG_CAPTURE_STATUS;
-               break;
-       case TYPE_VIA8233:
-               /* we use multi-channel playback mode, since this mode is supported
-                * by all VIA8233 models (and obviously suitable for our purpose).
-                */
-               chip->playback.reg_offset = VIA_REG_MULTPLAY_STATUS;
-               chip->capture.reg_offset = VIA_REG_CAPTURE_8233_STATUS;
-               break;
-       }
-
        if ((err = snd_via82xx_chip_init(chip)) < 0) {
                snd_via82xx_free(chip);
                return err;
@@ -1225,9 +1634,9 @@
        static int dev;
        snd_card_t *card;
        via82xx_t *chip;
-       int pcm_dev = 0;
+       unsigned char revision;
        int chip_type;
-       int err;
+       int i, err;
 
        if (dev >= SNDRV_CARDS)
                return -ENODEV;
@@ -1241,122 +1650,51 @@
                return -ENOMEM;
 
        chip_type = pci_id->driver_data;
+       pci_read_config_byte(pci, PCI_REVISION_ID, &revision);
        switch (chip_type) {
        case TYPE_VIA686:
                strcpy(card->driver, "VIA686A");
                strcpy(card->shortname, "VIA 82C686A/B");
                break;
        case TYPE_VIA8233:
-               strcpy(card->driver, "VIA8233");
-               strcpy(card->shortname, "VIA 8233A/C");
+               if (revision == VIA_REV_8233A) {
+                       strcpy(card->driver, "VIA8233A");
+                       strcpy(card->shortname, "VIA 8233A");
+               } else {
+                       strcpy(card->driver, "VIA8233");
+                       strcpy(card->shortname, "VIA 8233/C");
+               }
                break;
        default:
                snd_printk(KERN_ERR "invalid chip type %d\n", chip_type);
-               snd_card_free(card);
-               return -EINVAL;
+               err = -EINVAL;
+               goto __error;
        }
                
-       if ((err = snd_via82xx_create(card, pci, chip_type, ac97_clock[dev], &chip)) < 
0) {
-               snd_card_free(card);
-               return err;
-       }
-
-       if (snd_via82xx_mixer(chip) < 0) {
-               snd_card_free(card);
-               return err;
-       }
-       if (snd_via82xx_pcm(chip, pcm_dev++, NULL) < 0) {
-               snd_card_free(card);
-               return err;
-       }
-#if 0
-       if (snd_via82xx_pcm_fm(chip, pcm_dev++, NULL) < 0) {
-               snd_card_free(card);
-               return err;
-       }
-#endif
+       if ((err = snd_via82xx_create(card, pci, chip_type, ac97_clock[dev], &chip)) < 
+0)
+               goto __error;
 
-       if (chip->chip_type == TYPE_VIA686) {
-               unsigned char legacy, legacy_cfg;
-               int rev_h = 0;
-               legacy = chip->old_legacy;
-               legacy_cfg = chip->old_legacy_cfg;
-               legacy |= 0x40;         /* disable MIDI */
-               legacy &= ~0x08;        /* disable joystick */
-               if (chip->revision >= 0x20) {
-                       if (check_region(pci_resource_start(pci, 2), 4)) {
-                               rev_h = 0;
-                               legacy &= ~0x80;        /* disable PCI I/O 2 */
-                       } else {
-                               rev_h = 1;
-                               legacy |= 0x80;         /* enable PCI I/O 2 */
-                       }
-               }
-               pci_write_config_byte(pci, 0x42, legacy);
-               pci_write_config_byte(pci, 0x43, legacy_cfg);
-               if (rev_h && mpu_port[dev] >= 0x200) {  /* force MIDI */
-                       legacy |= 0x02; /* enable MPU */
-                       pci_write_config_dword(pci, 0x18, (mpu_port[dev] & 0xfffc) | 
0x01);
-               } else {
-                       if (rev_h && (legacy & 0x02)) {
-                               mpu_port[dev] = pci_resource_start(pci, 2);
-                               if (mpu_port[dev] < 0x200)      /* bad value */
-                                       legacy &= ~0x02;        /* disable MIDI */
-                       } else {
-                               switch (mpu_port[dev]) {        /* force MIDI */
-                               case 0x300:
-                               case 0x310:
-                               case 0x320:
-                               case 0x330:
-                                       legacy_cfg &= ~(3 << 2);
-                                       legacy_cfg |= (mpu_port[dev] & 0x0030) >> 2;
-                                       legacy |= 0x02;
-                                       break;
-                               default:                        /* no, use BIOS 
settings */
-                                       if (legacy & 0x02)
-                                               mpu_port[dev] = 0x300 + ((legacy_cfg & 
0x000c) << 2);
-                               }
-                       }
-               }
-               pci_write_config_byte(pci, 0x42, legacy);
-               pci_write_config_byte(pci, 0x43, legacy_cfg);
-               if (legacy & 0x02) {
-                       if (check_region(mpu_port[dev], 2)) {
-                               printk(KERN_WARNING "unable to get MPU-401 port at 
0x%lx, skipping\n", mpu_port[dev]);
-                               legacy &= ~0x02;
-                               pci_write_config_byte(pci, 0x42, legacy);
-                               goto __skip_mpu;
-                       }
-                       if (snd_mpu401_uart_new(card, 0, MPU401_HW_VIA686A,
-                                               mpu_port[dev], 0,
-                                               pci->irq, 0,
-                                               &chip->rmidi) < 0) {
-                               printk(KERN_WARNING "unable to initialize MPU-401 at 
0x%lx, skipping\n", mpu_port[dev]);
-                               legacy &= ~0x02;
-                               pci_write_config_byte(pci, 0x42, legacy);
-                               goto __skip_mpu;
-                       }
-                       legacy &= ~0x40;        /* enable MIDI interrupt */
-                       pci_write_config_byte(pci, 0x42, legacy);
-               __skip_mpu:
-                       ;
-               }
-       
-               /* card switches */
-               err = snd_ctl_add(card, snd_ctl_new1(&snd_via82xx_joystick_control, 
chip));
-               if (err < 0) {
-                       snd_card_free(card);
-                       return err;
-               }
+       if ((err = snd_via82xx_mixer_new(chip)) < 0)
+               goto __error;
 
+       if (chip_type == TYPE_VIA686) {
+               if ((err = snd_via686_pcm_new(chip)) < 0 ||
+                   (err = snd_via686_init_misc(chip, dev)) < 0)
+                       goto __error;
        } else {
-               /* VIA8233 */
-               err = snd_ctl_add(card, snd_ctl_new1(&snd_via8233_capture_source, 
chip));
-               if (err < 0) {
-                       snd_card_free(card);
-                       return err;
+               if (revision == VIA_REV_8233A) {
+                       if ((err = snd_via8233a_pcm_new(chip)) < 0)
+                               goto __error;
+               } else {
+                       if ((err = snd_via8233_pcm_new(chip)) < 0)
+                               goto __error;
                }
+               if ((err = snd_via8233_init_misc(chip, dev)) < 0)
+                       goto __error;
        }
+       /* disable interrupts */
+       for (i = 0; i < chip->num_devs; i++)
+               snd_via82xx_channel_reset(chip, &chip->devs[i]);
 
        sprintf(card->longname, "%s at 0x%lx, irq %d",
                card->shortname, chip->port, chip->irq);
@@ -1368,6 +1706,10 @@
        pci_set_drvdata(pci, card);
        dev++;
        return 0;
+
+ __error:
+       snd_card_free(card);
+       return err;
 }
 
 static void __devexit snd_via82xx_remove(struct pci_dev *pci)



-------------------------------------------------------
This SF.NET email is sponsored by:
SourceForge Enterprise Edition + IBM + LinuxWorld = Something 2 See!
http://www.vasoftware.com
_______________________________________________
Alsa-cvslog mailing list
[EMAIL PROTECTED]
https://lists.sourceforge.net/lists/listinfo/alsa-cvslog

Reply via email to