ALSA: azt3328: large codec cleanup, add I2S port etc.
authorAndreas Mohr <andi@lisas.de>
Sun, 5 Jul 2009 11:55:46 +0000 (13:55 +0200)
committerTakashi Iwai <tiwai@suse.de>
Mon, 6 Jul 2009 06:24:47 +0000 (08:24 +0200)
- fully separate codec I/O port handling, enabling the use of a single
  function each for all codecs (playback, capture, I2S out)
- add a new separate pcm for I2S out port (UNTESTED, no I2S DAC
  available yet)
- switch gameport to low frequency while idle, to try to reduce noise/power
- improve snd_azf3328_codec_setdmaa() calculation
- minor variable type cleanup (u16, bool etc.)
- add some doc updates (help those lost Windows users, debug help, ...)

Note that due to the large cleanup aspect of the codec I/O change,
I was able to fit everything including all improvements into the
same binary size!! (a measly 10 bytes more or so)

This should now be the almost last patch to this driver
(minus some possible kernel clocksource patch and x86_64 fixes or so).
I just felt like taking a break from the usual stuff and wanted to
get this driver's structure finished, and it's rather clean now...

Tested, working and checkpatch.pl:ed on 2.6.30-rc5,
applies cleanly to 2.6.30 proper.

Signed-off-by: Andreas Mohr <andi@lisas.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/azt3328.c
sound/pci/azt3328.h

index f290bc5..39dfdaa 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  azt3328.c - driver for Aztech AZF3328 based soundcards (e.g. PCI168).
- *  Copyright (C) 2002, 2005 - 2008 by Andreas Mohr <andi AT lisas.de>
+ *  Copyright (C) 2002, 2005 - 2009 by Andreas Mohr <andi AT lisas.de>
  *
  *  Framework borrowed from Bart Hartgers's als4000.c.
  *  Driver developed on PCI168 AP(W) version (PCI rev. 10, subsystem ID 1801),
  *  PCI168 A/AP, sub ID 8000
  *  Please give me feedback in case you try my driver with one of these!!
  *
+ *  Keywords: Windows XP Vista 168nt4-125.zip 168win95-125.zip PCI 168 download
+ *  (XP/Vista do not support this card at all but every Linux distribution
+ *   has very good support out of the box;
+ *   just to make sure that the right people hit this and get to know that,
+ *   despite the high level of Internet ignorance - as usual :-P -
+ *   about Linux support for this card)
+ *
  * GPL LICENSE
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  - built-in General DirectX timer having a 20 bits counter
  *    with 1us resolution (see below!)
  *  - I2S serial output port for external DAC
+ *    [FIXME: 3.3V or 5V level? maximum rate is 66.2kHz right?]
  *  - supports 33MHz PCI spec 2.1, PCI power management 1.0, compliant with ACPI
  *  - supports hardware volume control
  *  - single chip low cost solution (128 pin QFP)
- *  - supports programmable Sub-vendor and Sub-system ID
+ *  - supports programmable Sub-vendor and Sub-system ID [24C02 SEEPROM chip]
  *    required for Microsoft's logo compliance (FIXME: where?)
  *    At least the Trident 4D Wave DX has one bit somewhere
  *    to enable writes to PCI subsystem VID registers, that should be it.
@@ -82,6 +90,7 @@
  *    some custom data starting at 0x80. What kind of config settings
  *    are located in our extended PCI space anyway??
  *  - PCI168 AP(W) card: power amplifier with 4 Watts/channel at 4 Ohms
+ *    [TDA1517P chip]
  *
  *  Note that this driver now is actually *better* than the Windows driver,
  *  since it additionally supports the card's 1MHz DirectX timer - just try
  *    to read the Digital Enhanced Game Port. Not sure whether it is fixable.
  *
  * TODO
+ *  - use PCI_VDEVICE
+ *  - verify driver status on x86_64
+ *  - test multi-card driver operation
+ *  - (ab)use 1MHz DirectX timer as kernel clocksource
  *  - test MPU401 MIDI playback etc.
  *  - add more power micro-management (disable various units of the card
- *    as long as they're unused). However this requires more I/O ports which I
- *    haven't figured out yet and which thus might not even exist...
+ *    as long as they're unused, to improve audio quality and save power).
+ *    However this requires more I/O ports which I haven't figured out yet
+ *    and which thus might not even exist...
  *    The standard suspend/resume functionality could probably make use of
  *    some improvement, too...
  *  - figure out what all unknown port bits are responsible for
@@ -185,6 +199,26 @@ MODULE_SUPPORTED_DEVICE("{{Aztech,AZF3328}}");
 #define SUPPORT_GAMEPORT 1
 #endif
 
+/* === Debug settings ===
+  Further diagnostic functionality than the settings below
+  does not need to be provided, since one can easily write a bash script
+  to dump the card's I/O ports (those listed in lspci -v -v):
+  function dump()
+  {
+    local descr=$1; local addr=$2; local count=$3
+
+    echo "${descr}: ${count} @ ${addr}:"
+    dd if=/dev/port skip=$[${addr}] count=${count} bs=1 2>/dev/null| hexdump -C
+  }
+  and then use something like
+  "dump joy200 0x200 8", "dump mpu388 0x388 4", "dump joy 0xb400 8",
+  "dump codec00 0xa800 32", "dump mixer 0xb800 64", "dump synth 0xbc00 8",
+  possibly within a "while true; do ... sleep 1; done" loop.
+  Tweaking ports could be done using
+  VALSTRING="`printf "%02x" $value`"
+  printf "\x""$VALSTRING"|dd of=/dev/port seek=$[${addr}] bs=1 2>/dev/null
+*/
+
 #define DEBUG_MISC     0
 #define DEBUG_CALLS    0
 #define DEBUG_MIXER    0
@@ -250,22 +284,23 @@ static int seqtimer_scaling = 128;
 module_param(seqtimer_scaling, int, 0444);
 MODULE_PARM_DESC(seqtimer_scaling, "Set 1024000Hz sequencer timer scale factor (lockup danger!). Default 128.");
 
-struct snd_azf3328_audio_stream {
+struct snd_azf3328_codec_data {
+       unsigned long io_base;
        struct snd_pcm_substream *substream;
-       int enabled;
-       int running;
-       unsigned long portbase;
+       bool running;
+       const char *name;
 };
 
-enum snd_azf3328_stream_index {
-  AZF_PLAYBACK = 0,
-  AZF_CAPTURE = 1,
+enum snd_azf3328_codec_type {
+  AZF_CODEC_PLAYBACK = 0,
+  AZF_CODEC_CAPTURE = 1,
+  AZF_CODEC_I2S_OUT = 2,
 };
 
 struct snd_azf3328 {
        /* often-used fields towards beginning, then grouped */
 
-       unsigned long codec_io; /* usually 0xb000, size 128 */
+       unsigned long ctrl_io; /* usually 0xb000, size 128 */
        unsigned long game_io;  /* usually 0xb400, size 8 */
        unsigned long mpu_io;   /* usually 0xb800, size 4 */
        unsigned long opl3_io; /* usually 0xbc00, size 8 */
@@ -275,15 +310,17 @@ struct snd_azf3328 {
 
        struct snd_timer *timer;
 
-       struct snd_pcm *pcm;
-       struct snd_azf3328_audio_stream audio_stream[2];
+       struct snd_pcm *pcm[3];
+
+       /* playback, recording and I2S out codecs */
+       struct snd_azf3328_codec_data codecs[3];
 
        struct snd_card *card;
        struct snd_rawmidi *rmidi;
 
 #ifdef SUPPORT_GAMEPORT
        struct gameport *gameport;
-       int axes[4];
+       u16 axes[4];
 #endif
 
        struct pci_dev *pci;
@@ -293,12 +330,12 @@ struct snd_azf3328 {
         * If we need to add more registers here, then we might try to fold this
         * into some transparent combined shadow register handling with
         * CONFIG_PM register storage below, but that's slightly difficult. */
-       u16 shadow_reg_codec_6AH;
+       u16 shadow_reg_ctrl_6AH;
 
 #ifdef CONFIG_PM
        /* register value containers for power management
         * Note: not always full I/O range preserved (just like Win driver!) */
-       u16 saved_regs_codec[AZF_IO_SIZE_CODEC_PM / 2];
+       u16 saved_regs_ctrl[AZF_IO_SIZE_CTRL_PM / 2];
        u16 saved_regs_game [AZF_IO_SIZE_GAME_PM / 2];
        u16 saved_regs_mpu  [AZF_IO_SIZE_MPU_PM / 2];
        u16 saved_regs_opl3 [AZF_IO_SIZE_OPL3_PM / 2];
@@ -316,7 +353,7 @@ MODULE_DEVICE_TABLE(pci, snd_azf3328_ids);
 
 
 static int
-snd_azf3328_io_reg_setb(unsigned reg, u8 mask, int do_set)
+snd_azf3328_io_reg_setb(unsigned reg, u8 mask, bool do_set)
 {
        u8 prev = inb(reg), new;
 
@@ -331,39 +368,72 @@ snd_azf3328_io_reg_setb(unsigned reg, u8 mask, int do_set)
 }
 
 static inline void
-snd_azf3328_codec_outb(const struct snd_azf3328 *chip, unsigned reg, u8 value)
+snd_azf3328_codec_outb(const struct snd_azf3328_codec_data *codec,
+                      unsigned reg,
+                      u8 value
+)
 {
-       outb(value, chip->codec_io + reg);
+       outb(value, codec->io_base + reg);
 }
 
 static inline u8
-snd_azf3328_codec_inb(const struct snd_azf3328 *chip, unsigned reg)
+snd_azf3328_codec_inb(const struct snd_azf3328_codec_data *codec, unsigned reg)
 {
-       return inb(chip->codec_io + reg);
+       return inb(codec->io_base + reg);
 }
 
 static inline void
-snd_azf3328_codec_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value)
+snd_azf3328_codec_outw(const struct snd_azf3328_codec_data *codec,
+                      unsigned reg,
+                      u16 value
+)
 {
-       outw(value, chip->codec_io + reg);
+       outw(value, codec->io_base + reg);
 }
 
 static inline u16
-snd_azf3328_codec_inw(const struct snd_azf3328 *chip, unsigned reg)
+snd_azf3328_codec_inw(const struct snd_azf3328_codec_data *codec, unsigned reg)
 {
-       return inw(chip->codec_io + reg);
+       return inw(codec->io_base + reg);
 }
 
 static inline void
-snd_azf3328_codec_outl(const struct snd_azf3328 *chip, unsigned reg, u32 value)
+snd_azf3328_codec_outl(const struct snd_azf3328_codec_data *codec,
+                      unsigned reg,
+                      u32 value
+)
 {
-       outl(value, chip->codec_io + reg);
+       outl(value, codec->io_base + reg);
 }
 
 static inline u32
-snd_azf3328_codec_inl(const struct snd_azf3328 *chip, unsigned reg)
+snd_azf3328_codec_inl(const struct snd_azf3328_codec_data *codec, unsigned reg)
+{
+       return inl(codec->io_base + reg);
+}
+
+static inline void
+snd_azf3328_ctrl_outb(const struct snd_azf3328 *chip, unsigned reg, u8 value)
 {
-       return inl(chip->codec_io + reg);
+       outb(value, chip->ctrl_io + reg);
+}
+
+static inline u8
+snd_azf3328_ctrl_inb(const struct snd_azf3328 *chip, unsigned reg)
+{
+       return inb(chip->ctrl_io + reg);
+}
+
+static inline void
+snd_azf3328_ctrl_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value)
+{
+       outw(value, chip->ctrl_io + reg);
+}
+
+static inline void
+snd_azf3328_ctrl_outl(const struct snd_azf3328 *chip, unsigned reg, u32 value)
+{
+       outl(value, chip->ctrl_io + reg);
 }
 
 static inline void
@@ -404,13 +474,13 @@ snd_azf3328_mixer_inw(const struct snd_azf3328 *chip, unsigned reg)
 
 #define AZF_MUTE_BIT 0x80
 
-static int
+static bool
 snd_azf3328_mixer_set_mute(const struct snd_azf3328 *chip,
-                          unsigned reg, int do_mute
+                          unsigned reg, bool do_mute
 )
 {
        unsigned long portbase = chip->mixer_io + reg + 1;
-       int updated;
+       bool updated;
 
        /* the mute bit is on the *second* (i.e. right) register of a
         * left/right channel setting */
@@ -569,7 +639,7 @@ snd_azf3328_get_mixer(struct snd_kcontrol *kcontrol,
 {
        struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol);
        struct azf3328_mixer_reg reg;
-       unsigned int oreg, val;
+       u16 oreg, val;
 
        snd_azf3328_dbgcallenter();
        snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
@@ -600,7 +670,7 @@ snd_azf3328_put_mixer(struct snd_kcontrol *kcontrol,
 {
        struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol);
        struct azf3328_mixer_reg reg;
-       unsigned int oreg, nreg, val;
+       u16 oreg, nreg, val;
 
        snd_azf3328_dbgcallenter();
        snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
@@ -709,7 +779,7 @@ snd_azf3328_put_mixer_enum(struct snd_kcontrol *kcontrol,
 {
         struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol);
        struct azf3328_mixer_reg reg;
-       unsigned int oreg, nreg, val;
+       u16 oreg, nreg, val;
 
        snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
        oreg = snd_azf3328_mixer_inw(chip, reg.reg);
@@ -867,14 +937,15 @@ snd_azf3328_hw_free(struct snd_pcm_substream *substream)
 
 static void
 snd_azf3328_codec_setfmt(struct snd_azf3328 *chip,
-                              unsigned reg,
+                              enum snd_azf3328_codec_type codec_type,
                               enum azf_freq_t bitrate,
                               unsigned int format_width,
                               unsigned int channels
 )
 {
-       u16 val = 0xff00;
        unsigned long flags;
+       const struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type];
+       u16 val = 0xff00;
 
        snd_azf3328_dbgcallenter();
        switch (bitrate) {
@@ -917,7 +988,7 @@ snd_azf3328_codec_setfmt(struct snd_azf3328 *chip,
        spin_lock_irqsave(&chip->reg_lock, flags);
 
        /* set bitrate/format */
-       snd_azf3328_codec_outw(chip, reg, val);
+       snd_azf3328_codec_outw(codec, IDX_IO_CODEC_SOUNDFORMAT, val);
 
        /* changing the bitrate/format settings switches off the
         * audio output with an annoying click in case of 8/16bit format change
@@ -926,11 +997,11 @@ snd_azf3328_codec_setfmt(struct snd_azf3328 *chip,
         * (FIXME: yes, it works, but what exactly am I doing here?? :)
         * FIXME: does this have some side effects for full-duplex
         * or other dramatic side effects? */
-       if (reg == IDX_IO_PLAY_SOUNDFORMAT) /* only do it for playback */
-               snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS,
-                       snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS) |
-                       DMA_PLAY_SOMETHING1 |
-                       DMA_PLAY_SOMETHING2 |
+       if (codec_type == AZF_CODEC_PLAYBACK) /* only do it for playback */
+               snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS,
+                       snd_azf3328_codec_inw(codec, IDX_IO_CODEC_DMA_FLAGS) |
+                       DMA_RUN_SOMETHING1 |
+                       DMA_RUN_SOMETHING2 |
                        SOMETHING_ALMOST_ALWAYS_SET |
                        DMA_EPILOGUE_SOMETHING |
                        DMA_SOMETHING_ELSE
@@ -942,134 +1013,132 @@ snd_azf3328_codec_setfmt(struct snd_azf3328 *chip,
 
 static inline void
 snd_azf3328_codec_setfmt_lowpower(struct snd_azf3328 *chip,
-                           unsigned reg
+                           enum snd_azf3328_codec_type codec_type
 )
 {
        /* choose lowest frequency for low power consumption.
         * While this will cause louder noise due to rather coarse frequency,
         * it should never matter since output should always
         * get disabled properly when idle anyway. */
-       snd_azf3328_codec_setfmt(chip, reg, AZF_FREQ_4000, 8, 1);
+       snd_azf3328_codec_setfmt(chip, codec_type, AZF_FREQ_4000, 8, 1);
 }
 
 static void
-snd_azf3328_codec_reg_6AH_update(struct snd_azf3328 *chip,
+snd_azf3328_ctrl_reg_6AH_update(struct snd_azf3328 *chip,
                                        unsigned bitmask,
-                                       int enable
+                                       bool enable
 )
 {
        if (enable)
-               chip->shadow_reg_codec_6AH &= ~bitmask;
+               chip->shadow_reg_ctrl_6AH &= ~bitmask;
        else
-               chip->shadow_reg_codec_6AH |= bitmask;
+               chip->shadow_reg_ctrl_6AH |= bitmask;
        snd_azf3328_dbgplay("6AH_update mask 0x%04x enable %d: val 0x%04x\n",
-                       bitmask, enable, chip->shadow_reg_codec_6AH);
-       snd_azf3328_codec_outw(chip, IDX_IO_6AH, chip->shadow_reg_codec_6AH);
+                       bitmask, enable, chip->shadow_reg_ctrl_6AH);
+       snd_azf3328_ctrl_outw(chip, IDX_IO_6AH, chip->shadow_reg_ctrl_6AH);
 }
 
 static inline void
-snd_azf3328_codec_enable(struct snd_azf3328 *chip, int enable)
+snd_azf3328_ctrl_enable_codecs(struct snd_azf3328 *chip, bool enable)
 {
        snd_azf3328_dbgplay("codec_enable %d\n", enable);
        /* no idea what exactly is being done here, but I strongly assume it's
         * PM related */
-       snd_azf3328_codec_reg_6AH_update(
+       snd_azf3328_ctrl_reg_6AH_update(
                chip, IO_6A_PAUSE_PLAYBACK_BIT8, enable
        );
 }
 
 static void
-snd_azf3328_codec_activity(struct snd_azf3328 *chip,
-                               enum snd_azf3328_stream_index stream_type,
-                               int enable
+snd_azf3328_ctrl_codec_activity(struct snd_azf3328 *chip,
+                               enum snd_azf3328_codec_type codec_type,
+                               bool enable
 )
 {
-       int need_change = (chip->audio_stream[stream_type].running != enable);
+       struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type];
+       bool need_change = (codec->running != enable);
 
        snd_azf3328_dbgplay(
-               "codec_activity: type %d, enable %d, need_change %d\n",
-                               stream_type, enable, need_change
+               "codec_activity: %s codec, enable %d, need_change %d\n",
+                               codec->name, enable, need_change
        );
        if (need_change) {
-               enum snd_azf3328_stream_index other =
-                       (stream_type == AZF_PLAYBACK) ?
-                               AZF_CAPTURE : AZF_PLAYBACK;
-               /* small check to prevent shutting down the other party
-                * in case it's active */
-               if ((enable) || !(chip->audio_stream[other].running))
-                       snd_azf3328_codec_enable(chip, enable);
+               static const struct {
+                       enum snd_azf3328_codec_type other1;
+                       enum snd_azf3328_codec_type other2;
+               } peer_codecs[3] =
+                       { { AZF_CODEC_CAPTURE, AZF_CODEC_I2S_OUT },
+                         { AZF_CODEC_PLAYBACK, AZF_CODEC_I2S_OUT },
+                         { AZF_CODEC_PLAYBACK, AZF_CODEC_CAPTURE } };
+               bool call_function;
+
+               if (enable)
+                       /* if enable codec, call enable_codecs func
+                          to enable codec supply... */
+                       call_function = 1;
+               else {
+                       /* ...otherwise call enable_codecs func
+                          (which globally shuts down operation of codecs)
+                          only in case the other codecs are currently
+                          not active either! */
+                       if ((!chip->codecs[peer_codecs[codec_type].other1]
+                               .running)
+                       &&  (!chip->codecs[peer_codecs[codec_type].other2]
+                               .running))
+                               call_function = 1;
+                }
+                if (call_function)
+                       snd_azf3328_ctrl_enable_codecs(chip, enable);
 
                /* ...and adjust clock, too
                 * (reduce noise and power consumption) */
                if (!enable)
                        snd_azf3328_codec_setfmt_lowpower(
                                chip,
-                               chip->audio_stream[stream_type].portbase
-                                       + IDX_IO_PLAY_SOUNDFORMAT
+                               codec_type
                        );
        }
-       chip->audio_stream[stream_type].running = enable;
+       codec->running = enable;
 }
 
 static void
-snd_azf3328_setdmaa(struct snd_azf3328 *chip,
-                               long unsigned int addr,
-                                unsigned int count,
-                                unsigned int size,
-                               enum snd_azf3328_stream_index stream_type
+snd_azf3328_codec_setdmaa(struct snd_azf3328 *chip,
+                               enum snd_azf3328_codec_type codec_type,
+                               unsigned long addr,
+                               unsigned int count,
+                               unsigned int size
 )
 {
+       const struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type];
        snd_azf3328_dbgcallenter();
-       if (!chip->audio_stream[stream_type].running) {
-               /* AZF3328 uses a two buffer pointer DMA playback approach */
+       if (!codec->running) {
+               /* AZF3328 uses a two buffer pointer DMA transfer approach */
 
-               unsigned long flags, portbase, addr_area2;
+               unsigned long flags;
 
                /* width 32bit (prevent overflow): */
-               unsigned long count_areas, count_tmp;
+               u32 addr_area2, count_areas, lengths;
 
-               portbase = chip->audio_stream[stream_type].portbase;
                count_areas = size/2;
                addr_area2 = addr+count_areas;
                count_areas--; /* max. index */
                snd_azf3328_dbgplay("set DMA: buf1 %08lx[%lu], buf2 %08lx[%lu]\n", addr, count_areas, addr_area2, count_areas);
 
                /* build combined I/O buffer length word */
-               count_tmp = count_areas;
-               count_areas |= (count_tmp << 16);
+               lengths = (count_areas << 16) | (count_areas);
                spin_lock_irqsave(&chip->reg_lock, flags);
-               outl(addr, portbase + IDX_IO_PLAY_DMA_START_1);
-               outl(addr_area2, portbase + IDX_IO_PLAY_DMA_START_2);
-               outl(count_areas, portbase + IDX_IO_PLAY_DMA_LEN_1);
+               snd_azf3328_codec_outl(codec, IDX_IO_CODEC_DMA_START_1, addr);
+               snd_azf3328_codec_outl(codec, IDX_IO_CODEC_DMA_START_2,
+                                                               addr_area2);
+               snd_azf3328_codec_outl(codec, IDX_IO_CODEC_DMA_LENGTHS,
+                                                               lengths);
                spin_unlock_irqrestore(&chip->reg_lock, flags);
        }
        snd_azf3328_dbgcallleave();
 }
 
 static int
-snd_azf3328_playback_prepare(struct snd_pcm_substream *substream)
-{
-#if 0
-       struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
-       struct snd_pcm_runtime *runtime = substream->runtime;
-        unsigned int size = snd_pcm_lib_buffer_bytes(substream);
-       unsigned int count = snd_pcm_lib_period_bytes(substream);
-#endif
-
-       snd_azf3328_dbgcallenter();
-#if 0
-       snd_azf3328_codec_setfmt(chip, IDX_IO_PLAY_SOUNDFORMAT,
-               runtime->rate,
-               snd_pcm_format_width(runtime->format),
-               runtime->channels);
-       snd_azf3328_setdmaa(chip, runtime->dma_addr, count, size, AZF_PLAYBACK);
-#endif
-       snd_azf3328_dbgcallleave();
-       return 0;
-}
-
-static int
-snd_azf3328_capture_prepare(struct snd_pcm_substream *substream)
+snd_azf3328_codec_prepare(struct snd_pcm_substream *substream)
 {
 #if 0
        struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
@@ -1080,135 +1149,161 @@ snd_azf3328_capture_prepare(struct snd_pcm_substream *substream)
 
        snd_azf3328_dbgcallenter();
 #if 0
-       snd_azf3328_codec_setfmt(chip, IDX_IO_REC_SOUNDFORMAT,
+       snd_azf3328_codec_setfmt(chip, AZF_CODEC_...,
                runtime->rate,
                snd_pcm_format_width(runtime->format),
                runtime->channels);
-       snd_azf3328_setdmaa(chip, runtime->dma_addr, count, size, AZF_CAPTURE);
+       snd_azf3328_codec_setdmaa(chip, AZF_CODEC_...,
+                                       runtime->dma_addr, count, size);
 #endif
        snd_azf3328_dbgcallleave();
        return 0;
 }
 
 static int
-snd_azf3328_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+snd_azf3328_codec_trigger(enum snd_azf3328_codec_type codec_type,
+                       struct snd_pcm_substream *substream, int cmd)
 {
        struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
+       const struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type];
        struct snd_pcm_runtime *runtime = substream->runtime;
        int result = 0;
-       unsigned int status1;
-       int previously_muted;
+       u16 flags1;
+       bool previously_muted = 0;
+       bool is_playback_codec = (AZF_CODEC_PLAYBACK == codec_type);
 
-       snd_azf3328_dbgcalls("snd_azf3328_playback_trigger cmd %d\n", cmd);
+       snd_azf3328_dbgcalls("snd_azf3328_codec_trigger cmd %d\n", cmd);
 
        switch (cmd) {
        case SNDRV_PCM_TRIGGER_START:
-               snd_azf3328_dbgplay("START PLAYBACK\n");
-
-               /* mute WaveOut (avoid clicking during setup) */
-               previously_muted =
-                       snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1);
+               snd_azf3328_dbgplay("START %s\n", codec->name);
+
+               if (is_playback_codec) {
+                       /* mute WaveOut (avoid clicking during setup) */
+                       previously_muted =
+                               snd_azf3328_mixer_set_mute(
+                                               chip, IDX_MIXER_WAVEOUT, 1
+                               );
+               }
 
-               snd_azf3328_codec_setfmt(chip, IDX_IO_PLAY_SOUNDFORMAT,
+               snd_azf3328_codec_setfmt(chip, codec_type,
                        runtime->rate,
                        snd_pcm_format_width(runtime->format),
                        runtime->channels);
 
                spin_lock(&chip->reg_lock);
                /* first, remember current value: */
-               status1 = snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS);
+               flags1 = snd_azf3328_codec_inw(codec, IDX_IO_CODEC_DMA_FLAGS);
 
-               /* stop playback */
-               status1 &= ~DMA_RESUME;
-               snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1);
+               /* stop transfer */
+               flags1 &= ~DMA_RESUME;
+               snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1);
 
                /* FIXME: clear interrupts or what??? */
-               snd_azf3328_codec_outw(chip, IDX_IO_PLAY_IRQTYPE, 0xffff);
+               snd_azf3328_codec_outw(codec, IDX_IO_CODEC_IRQTYPE, 0xffff);
                spin_unlock(&chip->reg_lock);
 
-               snd_azf3328_setdmaa(chip, runtime->dma_addr,
+               snd_azf3328_codec_setdmaa(chip, codec_type, runtime->dma_addr,
                        snd_pcm_lib_period_bytes(substream),
-                       snd_pcm_lib_buffer_bytes(substream),
-                       AZF_PLAYBACK);
+                       snd_pcm_lib_buffer_bytes(substream)
+               );
 
                spin_lock(&chip->reg_lock);
 #ifdef WIN9X
                /* FIXME: enable playback/recording??? */
-               status1 |= DMA_PLAY_SOMETHING1 | DMA_PLAY_SOMETHING2;
-               snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1);
+               flags1 |= DMA_RUN_SOMETHING1 | DMA_RUN_SOMETHING2;
+               snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1);
 
-               /* start playback again */
+               /* start transfer again */
                /* FIXME: what is this value (0x0010)??? */
-               status1 |= DMA_RESUME | DMA_EPILOGUE_SOMETHING;
-               snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1);
+               flags1 |= DMA_RESUME | DMA_EPILOGUE_SOMETHING;
+               snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1);
 #else /* NT4 */
-               snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS,
+               snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS,
                        0x0000);
-               snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS,
-                       DMA_PLAY_SOMETHING1);
-               snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS,
-                       DMA_PLAY_SOMETHING1 |
-                       DMA_PLAY_SOMETHING2);
-               snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS,
+               snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS,
+                       DMA_RUN_SOMETHING1);
+               snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS,
+                       DMA_RUN_SOMETHING1 |
+                       DMA_RUN_SOMETHING2);
+               snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS,
                        DMA_RESUME |
                        SOMETHING_ALMOST_ALWAYS_SET |
                        DMA_EPILOGUE_SOMETHING |
                        DMA_SOMETHING_ELSE);
 #endif
                spin_unlock(&chip->reg_lock);
-               snd_azf3328_codec_activity(chip, AZF_PLAYBACK, 1);
-
-               /* now unmute WaveOut */
-               if (!previously_muted)
-                       snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 0);
+               snd_azf3328_ctrl_codec_activity(chip, codec_type, 1);
+
+               if (is_playback_codec) {
+                       /* now unmute WaveOut */
+                       if (!previously_muted)
+                               snd_azf3328_mixer_set_mute(
+                                               chip, IDX_MIXER_WAVEOUT, 0
+                               );
+               }
 
-               snd_azf3328_dbgplay("STARTED PLAYBACK\n");
+               snd_azf3328_dbgplay("STARTED %s\n", codec->name);
                break;
        case SNDRV_PCM_TRIGGER_RESUME:
-               snd_azf3328_dbgplay("RESUME PLAYBACK\n");
-               /* resume playback if we were active */
+               snd_azf3328_dbgplay("RESUME %s\n", codec->name);
+               /* resume codec if we were active */
                spin_lock(&chip->reg_lock);
-               if (chip->audio_stream[AZF_PLAYBACK].running)
-                       snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS,
-                               snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS) | DMA_RESUME);
+               if (codec->running)
+                       snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS,
+                               snd_azf3328_codec_inw(
+                                       codec, IDX_IO_CODEC_DMA_FLAGS
+                               ) | DMA_RESUME
+                       );
                spin_unlock(&chip->reg_lock);
                break;
        case SNDRV_PCM_TRIGGER_STOP:
-               snd_azf3328_dbgplay("STOP PLAYBACK\n");
-
-               /* mute WaveOut (avoid clicking during setup) */
-               previously_muted =
-                       snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1);
+               snd_azf3328_dbgplay("STOP %s\n", codec->name);
+
+               if (is_playback_codec) {
+                       /* mute WaveOut (avoid clicking during setup) */
+                       previously_muted =
+                               snd_azf3328_mixer_set_mute(
+                                               chip, IDX_MIXER_WAVEOUT, 1
+                               );
+               }
 
                spin_lock(&chip->reg_lock);
                /* first, remember current value: */
-               status1 = snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS);
+               flags1 = snd_azf3328_codec_inw(codec, IDX_IO_CODEC_DMA_FLAGS);
 
-               /* stop playback */
-               status1 &= ~DMA_RESUME;
-               snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1);
+               /* stop transfer */
+               flags1 &= ~DMA_RESUME;
+               snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1);
 
                /* hmm, is this really required? we're resetting the same bit
                 * immediately thereafter... */
-               status1 |= DMA_PLAY_SOMETHING1;
-               snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1);
+               flags1 |= DMA_RUN_SOMETHING1;
+               snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1);
 
-               status1 &= ~DMA_PLAY_SOMETHING1;
-               snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1);
+               flags1 &= ~DMA_RUN_SOMETHING1;
+               snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1);
                spin_unlock(&chip->reg_lock);
-               snd_azf3328_codec_activity(chip, AZF_PLAYBACK, 0);
-
-               /* now unmute WaveOut */
-               if (!previously_muted)
-                       snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 0);
+               snd_azf3328_ctrl_codec_activity(chip, codec_type, 0);
+
+               if (is_playback_codec) {
+                       /* now unmute WaveOut */
+                       if (!previously_muted)
+                               snd_azf3328_mixer_set_mute(
+                                               chip, IDX_MIXER_WAVEOUT, 0
+                               );
+               }
 
-               snd_azf3328_dbgplay("STOPPED PLAYBACK\n");
+               snd_azf3328_dbgplay("STOPPED %s\n", codec->name);
                break;
        case SNDRV_PCM_TRIGGER_SUSPEND:
-               snd_azf3328_dbgplay("SUSPEND PLAYBACK\n");
-               /* make sure playback is stopped */
-               snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS,
-                       snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS) & ~DMA_RESUME);
+               snd_azf3328_dbgplay("SUSPEND %s\n", codec->name);
+               /* make sure codec is stopped */
+               snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS,
+                       snd_azf3328_codec_inw(
+                               codec, IDX_IO_CODEC_DMA_FLAGS
+                       ) & ~DMA_RESUME
+               );
                break;
         case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
                snd_printk(KERN_ERR "FIXME: SNDRV_PCM_TRIGGER_PAUSE_PUSH NIY!\n");
@@ -1225,172 +1320,74 @@ snd_azf3328_playback_trigger(struct snd_pcm_substream *substream, int cmd)
        return result;
 }
 
-/* this is just analogous to playback; I'm not quite sure whether recording
- * should actually be triggered like that */
 static int
-snd_azf3328_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+snd_azf3328_codec_playback_trigger(struct snd_pcm_substream *substream, int cmd)
 {
-       struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
-       struct snd_pcm_runtime *runtime = substream->runtime;
-       int result = 0;
-       unsigned int status1;
-
-       snd_azf3328_dbgcalls("snd_azf3328_capture_trigger cmd %d\n", cmd);
-
-        switch (cmd) {
-        case SNDRV_PCM_TRIGGER_START:
-
-               snd_azf3328_dbgplay("START CAPTURE\n");
-
-               snd_azf3328_codec_setfmt(chip, IDX_IO_REC_SOUNDFORMAT,
-                       runtime->rate,
-                       snd_pcm_format_width(runtime->format),
-                       runtime->channels);
-
-               spin_lock(&chip->reg_lock);
-               /* first, remember current value: */
-               status1 = snd_azf3328_codec_inw(chip, IDX_IO_REC_FLAGS);
-
-               /* stop recording */
-               status1 &= ~DMA_RESUME;
-               snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, status1);
-
-               /* FIXME: clear interrupts or what??? */
-               snd_azf3328_codec_outw(chip, IDX_IO_REC_IRQTYPE, 0xffff);
-               spin_unlock(&chip->reg_lock);
-
-               snd_azf3328_setdmaa(chip, runtime->dma_addr,
-                       snd_pcm_lib_period_bytes(substream),
-                       snd_pcm_lib_buffer_bytes(substream),
-                       AZF_CAPTURE);
-
-               spin_lock(&chip->reg_lock);
-#ifdef WIN9X
-               /* FIXME: enable playback/recording??? */
-               status1 |= DMA_PLAY_SOMETHING1 | DMA_PLAY_SOMETHING2;
-               snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, status1);
-
-               /* start capture again */
-               /* FIXME: what is this value (0x0010)??? */
-               status1 |= DMA_RESUME | DMA_EPILOGUE_SOMETHING;
-               snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, status1);
-#else
-               snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS,
-                       0x0000);
-               snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS,
-                       DMA_PLAY_SOMETHING1);
-               snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS,
-                       DMA_PLAY_SOMETHING1 |
-                       DMA_PLAY_SOMETHING2);
-               snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS,
-                       DMA_RESUME |
-                       SOMETHING_ALMOST_ALWAYS_SET |
-                       DMA_EPILOGUE_SOMETHING |
-                       DMA_SOMETHING_ELSE);
-#endif
-               spin_unlock(&chip->reg_lock);
-               snd_azf3328_codec_activity(chip, AZF_CAPTURE, 1);
-
-               snd_azf3328_dbgplay("STARTED CAPTURE\n");
-               break;
-       case SNDRV_PCM_TRIGGER_RESUME:
-               snd_azf3328_dbgplay("RESUME CAPTURE\n");
-               /* resume recording if we were active */
-               spin_lock(&chip->reg_lock);
-               if (chip->audio_stream[AZF_CAPTURE].running)
-                       snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS,
-                               snd_azf3328_codec_inw(chip, IDX_IO_REC_FLAGS) | DMA_RESUME);
-               spin_unlock(&chip->reg_lock);
-               break;
-        case SNDRV_PCM_TRIGGER_STOP:
-               snd_azf3328_dbgplay("STOP CAPTURE\n");
-
-               spin_lock(&chip->reg_lock);
-               /* first, remember current value: */
-               status1 = snd_azf3328_codec_inw(chip, IDX_IO_REC_FLAGS);
-
-               /* stop recording */
-               status1 &= ~DMA_RESUME;
-               snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, status1);
-
-               status1 |= DMA_PLAY_SOMETHING1;
-               snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, status1);
-
-               status1 &= ~DMA_PLAY_SOMETHING1;
-               snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, status1);
-               spin_unlock(&chip->reg_lock);
-               snd_azf3328_codec_activity(chip, AZF_CAPTURE, 0);
+       return snd_azf3328_codec_trigger(AZF_CODEC_PLAYBACK, substream, cmd);
+}
 
-               snd_azf3328_dbgplay("STOPPED CAPTURE\n");
-               break;
-       case SNDRV_PCM_TRIGGER_SUSPEND:
-               snd_azf3328_dbgplay("SUSPEND CAPTURE\n");
-               /* make sure recording is stopped */
-               snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS,
-                       snd_azf3328_codec_inw(chip, IDX_IO_REC_FLAGS) & ~DMA_RESUME);
-               break;
-        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
-               snd_printk(KERN_ERR "FIXME: SNDRV_PCM_TRIGGER_PAUSE_PUSH NIY!\n");
-                break;
-        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
-               snd_printk(KERN_ERR "FIXME: SNDRV_PCM_TRIGGER_PAUSE_RELEASE NIY!\n");
-                break;
-        default:
-               printk(KERN_ERR "FIXME: unknown trigger mode!\n");
-                return -EINVAL;
-       }
+static int
+snd_azf3328_codec_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+       return snd_azf3328_codec_trigger(AZF_CODEC_CAPTURE, substream, cmd);
+}
 
-       snd_azf3328_dbgcallleave();
-       return result;
+static int
+snd_azf3328_codec_i2s_out_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+       return snd_azf3328_codec_trigger(AZF_CODEC_I2S_OUT, substream, cmd);
 }
 
 static snd_pcm_uframes_t
-snd_azf3328_playback_pointer(struct snd_pcm_substream *substream)
+snd_azf3328_codec_pointer(struct snd_pcm_substream *substream,
+                         enum snd_azf3328_codec_type codec_type
+)
 {
-       struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
+       const struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
+       const struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type];
        unsigned long bufptr, result;
        snd_pcm_uframes_t frmres;
 
 #ifdef QUERY_HARDWARE
-       bufptr = snd_azf3328_codec_inl(chip, IDX_IO_PLAY_DMA_START_1);
+       bufptr = snd_azf3328_codec_inl(codec, IDX_IO_CODEC_DMA_START_1);
 #else
        bufptr = substream->runtime->dma_addr;
 #endif
-       result = snd_azf3328_codec_inl(chip, IDX_IO_PLAY_DMA_CURRPOS);
+       result = snd_azf3328_codec_inl(codec, IDX_IO_CODEC_DMA_CURRPOS);
 
        /* calculate offset */
        result -= bufptr;
        frmres = bytes_to_frames( substream->runtime, result);
-       snd_azf3328_dbgplay("PLAY @ 0x%8lx, frames %8ld\n", result, frmres);
+       snd_azf3328_dbgplay("%s @ 0x%8lx, frames %8ld\n",
+                               codec->name, result, frmres);
        return frmres;
 }
 
 static snd_pcm_uframes_t
-snd_azf3328_capture_pointer(struct snd_pcm_substream *substream)
+snd_azf3328_codec_playback_pointer(struct snd_pcm_substream *substream)
 {
-       struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
-       unsigned long bufptr, result;
-       snd_pcm_uframes_t frmres;
+       return snd_azf3328_codec_pointer(substream, AZF_CODEC_PLAYBACK);
+}
 
-#ifdef QUERY_HARDWARE
-       bufptr = snd_azf3328_codec_inl(chip, IDX_IO_REC_DMA_START_1);
-#else
-       bufptr = substream->runtime->dma_addr;
-#endif
-       result = snd_azf3328_codec_inl(chip, IDX_IO_REC_DMA_CURRPOS);
+static snd_pcm_uframes_t
+snd_azf3328_codec_capture_pointer(struct snd_pcm_substream *substream)
+{
+       return snd_azf3328_codec_pointer(substream, AZF_CODEC_CAPTURE);
+}
 
-       /* calculate offset */
-       result -= bufptr;
-       frmres = bytes_to_frames( substream->runtime, result);
-       snd_azf3328_dbgplay("REC  @ 0x%8lx, frames %8ld\n", result, frmres);
-       return frmres;
+static snd_pcm_uframes_t
+snd_azf3328_codec_i2s_out_pointer(struct snd_pcm_substream *substream)
+{
+       return snd_azf3328_codec_pointer(substream, AZF_CODEC_I2S_OUT);
 }
 
 /******************************************************************/
 
 #ifdef SUPPORT_GAMEPORT
 static inline void
-snd_azf3328_gameport_irq_enable(struct snd_azf3328 *chip, int enable)
+snd_azf3328_gameport_irq_enable(struct snd_azf3328 *chip,
+                               bool enable
+)
 {
        snd_azf3328_io_reg_setb(
                chip->game_io+IDX_GAME_HWCONFIG,
@@ -1400,7 +1397,9 @@ snd_azf3328_gameport_irq_enable(struct snd_azf3328 *chip, int enable)
 }
 
 static inline void
-snd_azf3328_gameport_legacy_address_enable(struct snd_azf3328 *chip, int enable)
+snd_azf3328_gameport_legacy_address_enable(struct snd_azf3328 *chip,
+                                          bool enable
+)
 {
        snd_azf3328_io_reg_setb(
                chip->game_io+IDX_GAME_HWCONFIG,
@@ -1409,10 +1408,27 @@ snd_azf3328_gameport_legacy_address_enable(struct snd_azf3328 *chip, int enable)
        );
 }
 
+static void
+snd_azf3328_gameport_set_counter_frequency(struct snd_azf3328 *chip,
+                                          unsigned int freq_cfg
+)
+{
+       snd_azf3328_io_reg_setb(
+               chip->game_io+IDX_GAME_HWCONFIG,
+               0x02,
+               (freq_cfg & 1) != 0
+       );
+       snd_azf3328_io_reg_setb(
+               chip->game_io+IDX_GAME_HWCONFIG,
+               0x04,
+               (freq_cfg & 2) != 0
+       );
+}
+
 static inline void
-snd_azf3328_gameport_axis_circuit_enable(struct snd_azf3328 *chip, int enable)
+snd_azf3328_gameport_axis_circuit_enable(struct snd_azf3328 *chip, bool enable)
 {
-       snd_azf3328_codec_reg_6AH_update(
+       snd_azf3328_ctrl_reg_6AH_update(
                chip, IO_6A_SOMETHING2_GAMEPORT, enable
        );
 }
@@ -1447,6 +1463,8 @@ snd_azf3328_gameport_open(struct gameport *gameport, int mode)
                break;
        }
 
+       snd_azf3328_gameport_set_counter_frequency(chip,
+                               GAME_HWCFG_ADC_COUNTER_FREQ_STD);
        snd_azf3328_gameport_axis_circuit_enable(chip, (res == 0));
 
        return res;
@@ -1458,6 +1476,8 @@ snd_azf3328_gameport_close(struct gameport *gameport)
        struct snd_azf3328 *chip = gameport_get_port_data(gameport);
 
        snd_azf3328_dbggame("gameport_close\n");
+       snd_azf3328_gameport_set_counter_frequency(chip,
+                               GAME_HWCFG_ADC_COUNTER_FREQ_1_200);
        snd_azf3328_gameport_axis_circuit_enable(chip, 0);
 }
 
@@ -1491,7 +1511,7 @@ snd_azf3328_gameport_cooked_read(struct gameport *gameport,
 
        val = snd_azf3328_game_inb(chip, IDX_GAME_AXES_CONFIG);
        if (val & GAME_AXES_SAMPLING_READY) {
-               for (i = 0; i < 4; ++i) {
+               for (i = 0; i < ARRAY_SIZE(chip->axes); ++i) {
                        /* configure the axis to read */
                        val = (i << 4) | 0x0f;
                        snd_azf3328_game_outb(chip, IDX_GAME_AXES_CONFIG, val);
@@ -1514,7 +1534,7 @@ snd_azf3328_gameport_cooked_read(struct gameport *gameport,
        snd_azf3328_game_outw(chip, IDX_GAME_AXIS_VALUE, 0xffff);
        spin_unlock_irqrestore(&chip->reg_lock, flags);
 
-       for (i = 0; i < 4; i++) {
+       for (i = 0; i < ARRAY_SIZE(chip->axes); i++) {
                axes[i] = chip->axes[i];
                if (axes[i] == 0xffff)
                        axes[i] = -1;
@@ -1552,6 +1572,8 @@ snd_azf3328_gameport(struct snd_azf3328 *chip, int dev)
        /* DISABLE legacy address: we don't need it! */
        snd_azf3328_gameport_legacy_address_enable(chip, 0);
 
+       snd_azf3328_gameport_set_counter_frequency(chip,
+                               GAME_HWCFG_ADC_COUNTER_FREQ_1_200);
        snd_azf3328_gameport_axis_circuit_enable(chip, 0);
 
        gameport_register_port(chip->gameport);
@@ -1591,29 +1613,69 @@ snd_azf3328_irq_log_unknown_type(u8 which)
        );
 }
 
+static inline void
+snd_azf3328_codec_interrupt(struct snd_azf3328 *chip, u8 status)
+{
+       u8 which;
+       enum snd_azf3328_codec_type codec_type;
+       const struct snd_azf3328_codec_data *codec;
+
+       for (codec_type = AZF_CODEC_PLAYBACK;
+                codec_type <= AZF_CODEC_I2S_OUT;
+                        ++codec_type) {
+
+               /* skip codec if there's no interrupt for it */
+               if (!(status & (1 << codec_type)))
+                       continue;
+
+               codec = &chip->codecs[codec_type];
+
+               spin_lock(&chip->reg_lock);
+               which = snd_azf3328_codec_inb(codec, IDX_IO_CODEC_IRQTYPE);
+               /* ack all IRQ types immediately */
+               snd_azf3328_codec_outb(codec, IDX_IO_CODEC_IRQTYPE, which);
+               spin_unlock(&chip->reg_lock);
+
+               if ((chip->pcm[codec_type])
+               &&  (chip->codecs[codec_type].substream)) {
+                       snd_pcm_period_elapsed(
+                               chip->codecs[codec_type].substream
+                       );
+                       snd_azf3328_dbgplay("%s period done (#%x), @ %x\n",
+                               codec->name,
+                               which,
+                               snd_azf3328_codec_inl(
+                                       codec, IDX_IO_CODEC_DMA_CURRPOS
+                               )
+                       );
+               } else
+                       printk(KERN_WARNING "azt3328: irq handler problem!\n");
+               if (which & IRQ_SOMETHING)
+                       snd_azf3328_irq_log_unknown_type(which);
+       }
+}
+
 static irqreturn_t
 snd_azf3328_interrupt(int irq, void *dev_id)
 {
        struct snd_azf3328 *chip = dev_id;
-       u8 status, which;
+       u8 status;
 #if DEBUG_PLAY_REC
        static unsigned long irq_count;
 #endif
 
-       status = snd_azf3328_codec_inb(chip, IDX_IO_IRQSTATUS);
+       status = snd_azf3328_ctrl_inb(chip, IDX_IO_IRQSTATUS);
 
         /* fast path out, to ease interrupt sharing */
        if (!(status &
-               (IRQ_PLAYBACK|IRQ_RECORDING|IRQ_GAMEPORT|IRQ_MPU401|IRQ_TIMER)
+               (IRQ_PLAYBACK|IRQ_RECORDING|IRQ_I2S_OUT
+               |IRQ_GAMEPORT|IRQ_MPU401|IRQ_TIMER)
        ))
                return IRQ_NONE; /* must be interrupt for another device */
 
        snd_azf3328_dbgplay(
-               "irq_count %ld! IDX_IO_PLAY_FLAGS %04x, "
-               "IDX_IO_PLAY_IRQTYPE %04x, IDX_IO_IRQSTATUS %04x\n",
+               "irq_count %ld! IDX_IO_IRQSTATUS %04x\n",
                        irq_count++ /* debug-only */,
-                       snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS),
-                       snd_azf3328_codec_inw(chip, IDX_IO_PLAY_IRQTYPE),
                        status
        );
 
@@ -1626,63 +1688,24 @@ snd_azf3328_interrupt(int irq, void *dev_id)
                        snd_timer_interrupt(chip->timer, chip->timer->sticks);
                /* ACK timer */
                 spin_lock(&chip->reg_lock);
-               snd_azf3328_codec_outb(chip, IDX_IO_TIMER_VALUE + 3, 0x07);
+               snd_azf3328_ctrl_outb(chip, IDX_IO_TIMER_VALUE + 3, 0x07);
                spin_unlock(&chip->reg_lock);
                snd_azf3328_dbgplay("azt3328: timer IRQ\n");
        }
-       if (status & IRQ_PLAYBACK) {
-               spin_lock(&chip->reg_lock);
-               which = snd_azf3328_codec_inb(chip, IDX_IO_PLAY_IRQTYPE);
-               /* ack all IRQ types immediately */
-               snd_azf3328_codec_outb(chip, IDX_IO_PLAY_IRQTYPE, which);
-                       spin_unlock(&chip->reg_lock);
 
-               if (chip->pcm && chip->audio_stream[AZF_PLAYBACK].substream) {
-                       snd_pcm_period_elapsed(
-                               chip->audio_stream[AZF_PLAYBACK].substream
-                       );
-                       snd_azf3328_dbgplay("PLAY period done (#%x), @ %x\n",
-                               which,
-                               snd_azf3328_codec_inl(
-                                       chip, IDX_IO_PLAY_DMA_CURRPOS
-                               )
-                       );
-               } else
-                       printk(KERN_WARNING "azt3328: irq handler problem!\n");
-               if (which & IRQ_PLAY_SOMETHING)
-                       snd_azf3328_irq_log_unknown_type(which);
-       }
-       if (status & IRQ_RECORDING) {
-                spin_lock(&chip->reg_lock);
-               which = snd_azf3328_codec_inb(chip, IDX_IO_REC_IRQTYPE);
-               /* ack all IRQ types immediately */
-               snd_azf3328_codec_outb(chip, IDX_IO_REC_IRQTYPE, which);
-               spin_unlock(&chip->reg_lock);
+       if (status & (IRQ_PLAYBACK|IRQ_RECORDING|IRQ_I2S_OUT))
+               snd_azf3328_codec_interrupt(chip, status);
 
-               if (chip->pcm && chip->audio_stream[AZF_CAPTURE].substream) {
-                       snd_pcm_period_elapsed(
-                               chip->audio_stream[AZF_CAPTURE].substream
-                       );
-                       snd_azf3328_dbgplay("REC  period done (#%x), @ %x\n",
-                               which,
-                               snd_azf3328_codec_inl(
-                                       chip, IDX_IO_REC_DMA_CURRPOS
-                               )
-                       );
-               } else
-                       printk(KERN_WARNING "azt3328: irq handler problem!\n");
-               if (which & IRQ_REC_SOMETHING)
-                       snd_azf3328_irq_log_unknown_type(which);
-       }
        if (status & IRQ_GAMEPORT)
                snd_azf3328_gameport_interrupt(chip);
+
        /* MPU401 has less critical IRQ requirements
         * than timer and playback/recording, right? */
        if (status & IRQ_MPU401) {
                snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data);
 
                /* hmm, do we have to ack the IRQ here somehow?
-                * If so, then I don't know how... */
+                * If so, then I don't know how yet... */
                snd_azf3328_dbgplay("azt3328: MPU401 IRQ\n");
        }
        return IRQ_HANDLED;
@@ -1690,7 +1713,11 @@ snd_azf3328_interrupt(int irq, void *dev_id)
 
 /*****************************************************************/
 
-static const struct snd_pcm_hardware snd_azf3328_playback =
+/* as long as we think we have identical snd_pcm_hardware parameters
+   for playback, capture and i2s out, we can use the same physical struct
+   since the struct is simply being copied into a member.
+*/
+static const struct snd_pcm_hardware snd_azf3328_hardware =
 {
        /* FIXME!! Correct? */
        .info =                 SNDRV_PCM_INFO_MMAP |
@@ -1718,31 +1745,6 @@ static const struct snd_pcm_hardware snd_azf3328_playback =
        .fifo_size =            0,
 };
 
-static const struct snd_pcm_hardware snd_azf3328_capture =
-{
-       /* FIXME */
-       .info =                 SNDRV_PCM_INFO_MMAP |
-                               SNDRV_PCM_INFO_INTERLEAVED |
-                               SNDRV_PCM_INFO_MMAP_VALID,
-       .formats =              SNDRV_PCM_FMTBIT_S8 |
-                               SNDRV_PCM_FMTBIT_U8 |
-                               SNDRV_PCM_FMTBIT_S16_LE |
-                               SNDRV_PCM_FMTBIT_U16_LE,
-       .rates =                SNDRV_PCM_RATE_5512 |
-                               SNDRV_PCM_RATE_8000_48000 |
-                               SNDRV_PCM_RATE_KNOT,
-       .rate_min =             AZF_FREQ_4000,
-       .rate_max =             AZF_FREQ_66200,
-       .channels_min =         1,
-       .channels_max =         2,
-       .buffer_bytes_max =     65536,
-       .period_bytes_min =     64,
-       .period_bytes_max =     65536,
-       .periods_min =          1,
-       .periods_max =          1024,
-       .fifo_size =            0,
-};
-
 
 static unsigned int snd_azf3328_fixed_rates[] = {
        AZF_FREQ_4000,
@@ -1770,55 +1772,72 @@ static struct snd_pcm_hw_constraint_list snd_azf3328_hw_constraints_rates = {
 /*****************************************************************/
 
 static int
-snd_azf3328_playback_open(struct snd_pcm_substream *substream)
+snd_azf3328_pcm_open(struct snd_pcm_substream *substream,
+                    enum snd_azf3328_codec_type codec_type
+)
 {
        struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
        struct snd_pcm_runtime *runtime = substream->runtime;
 
        snd_azf3328_dbgcallenter();
-       chip->audio_stream[AZF_PLAYBACK].substream = substream;
-       runtime->hw = snd_azf3328_playback;
+       chip->codecs[codec_type].substream = substream;
+
+       /* same parameters for all our codecs - at least we think so... */
+       runtime->hw = snd_azf3328_hardware;
+
        snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
                                   &snd_azf3328_hw_constraints_rates);
        snd_azf3328_dbgcallleave();
        return 0;
 }
 
+static int
+snd_azf3328_playback_open(struct snd_pcm_substream *substream)
+{
+       return snd_azf3328_pcm_open(substream, AZF_CODEC_PLAYBACK);
+}
+
 static int
 snd_azf3328_capture_open(struct snd_pcm_substream *substream)
 {
-       struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
-       struct snd_pcm_runtime *runtime = substream->runtime;
+       return snd_azf3328_pcm_open(substream, AZF_CODEC_CAPTURE);
+}
 
-       snd_azf3328_dbgcallenter();
-       chip->audio_stream[AZF_CAPTURE].substream = substream;
-       runtime->hw = snd_azf3328_capture;
-       snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
-                                  &snd_azf3328_hw_constraints_rates);
-       snd_azf3328_dbgcallleave();
-       return 0;
+static int
+snd_azf3328_i2s_out_open(struct snd_pcm_substream *substream)
+{
+       return snd_azf3328_pcm_open(substream, AZF_CODEC_I2S_OUT);
 }
 
 static int
-snd_azf3328_playback_close(struct snd_pcm_substream *substream)
+snd_azf3328_pcm_close(struct snd_pcm_substream *substream,
+                     enum snd_azf3328_codec_type codec_type
+)
 {
        struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
 
        snd_azf3328_dbgcallenter();
-       chip->audio_stream[AZF_PLAYBACK].substream = NULL;
+       chip->codecs[codec_type].substream = NULL;
        snd_azf3328_dbgcallleave();
        return 0;
 }
 
+static int
+snd_azf3328_playback_close(struct snd_pcm_substream *substream)
+{
+       return snd_azf3328_pcm_close(substream, AZF_CODEC_PLAYBACK);
+}
+
 static int
 snd_azf3328_capture_close(struct snd_pcm_substream *substream)
 {
-       struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
+       return snd_azf3328_pcm_close(substream, AZF_CODEC_CAPTURE);
+}
 
-       snd_azf3328_dbgcallenter();
-       chip->audio_stream[AZF_CAPTURE].substream = NULL;
-       snd_azf3328_dbgcallleave();
-       return 0;
+static int
+snd_azf3328_i2s_out_close(struct snd_pcm_substream *substream)
+{
+       return snd_azf3328_pcm_close(substream, AZF_CODEC_I2S_OUT);
 }
 
 /******************************************************************/
@@ -1829,9 +1848,9 @@ static struct snd_pcm_ops snd_azf3328_playback_ops = {
        .ioctl =        snd_pcm_lib_ioctl,
        .hw_params =    snd_azf3328_hw_params,
        .hw_free =      snd_azf3328_hw_free,
-       .prepare =      snd_azf3328_playback_prepare,
-       .trigger =      snd_azf3328_playback_trigger,
-       .pointer =      snd_azf3328_playback_pointer
+       .prepare =      snd_azf3328_codec_prepare,
+       .trigger =      snd_azf3328_codec_playback_trigger,
+       .pointer =      snd_azf3328_codec_playback_pointer
 };
 
 static struct snd_pcm_ops snd_azf3328_capture_ops = {
@@ -1840,30 +1859,67 @@ static struct snd_pcm_ops snd_azf3328_capture_ops = {
        .ioctl =        snd_pcm_lib_ioctl,
        .hw_params =    snd_azf3328_hw_params,
        .hw_free =      snd_azf3328_hw_free,
-       .prepare =      snd_azf3328_capture_prepare,
-       .trigger =      snd_azf3328_capture_trigger,
-       .pointer =      snd_azf3328_capture_pointer
+       .prepare =      snd_azf3328_codec_prepare,
+       .trigger =      snd_azf3328_codec_capture_trigger,
+       .pointer =      snd_azf3328_codec_capture_pointer
+};
+
+static struct snd_pcm_ops snd_azf3328_i2s_out_ops = {
+       .open =         snd_azf3328_i2s_out_open,
+       .close =        snd_azf3328_i2s_out_close,
+       .ioctl =        snd_pcm_lib_ioctl,
+       .hw_params =    snd_azf3328_hw_params,
+       .hw_free =      snd_azf3328_hw_free,
+       .prepare =      snd_azf3328_codec_prepare,
+       .trigger =      snd_azf3328_codec_i2s_out_trigger,
+       .pointer =      snd_azf3328_codec_i2s_out_pointer
 };
 
 static int __devinit
-snd_azf3328_pcm(struct snd_azf3328 *chip, int device)
+snd_azf3328_pcm(struct snd_azf3328 *chip)
 {
+enum { AZF_PCMDEV_STD, AZF_PCMDEV_I2S_OUT, NUM_AZF_PCMDEVS }; /* pcm devices */
+
        struct snd_pcm *pcm;
        int err;
 
        snd_azf3328_dbgcallenter();
-       if ((err = snd_pcm_new(chip->card, "AZF3328 DSP", device, 1, 1, &pcm)) < 0)
+
+       err = snd_pcm_new(chip->card, "AZF3328 DSP", AZF_PCMDEV_STD,
+                                                               1, 1, &pcm);
+       if (err < 0)
                return err;
-       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_azf3328_playback_ops);
-       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_azf3328_capture_ops);
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+                                               &snd_azf3328_playback_ops);
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+                                               &snd_azf3328_capture_ops);
 
        pcm->private_data = chip;
        pcm->info_flags = 0;
        strcpy(pcm->name, chip->card->shortname);
-       chip->pcm = pcm;
+       /* same pcm object for playback/capture (see snd_pcm_new() above) */
+       chip->pcm[AZF_CODEC_PLAYBACK] = pcm;
+       chip->pcm[AZF_CODEC_CAPTURE] = pcm;
 
        snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
-                                             snd_dma_pci_data(chip->pci), 64*1024, 64*1024);
+                                               snd_dma_pci_data(chip->pci),
+                                                       64*1024, 64*1024);
+
+       err = snd_pcm_new(chip->card, "AZF3328 I2S OUT", AZF_PCMDEV_I2S_OUT,
+                                                               1, 0, &pcm);
+       if (err < 0)
+               return err;
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+                                               &snd_azf3328_i2s_out_ops);
+
+       pcm->private_data = chip;
+       pcm->info_flags = 0;
+       strcpy(pcm->name, chip->card->shortname);
+       chip->pcm[AZF_CODEC_I2S_OUT] = pcm;
+
+       snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+                                               snd_dma_pci_data(chip->pci),
+                                                       64*1024, 64*1024);
 
        snd_azf3328_dbgcallleave();
        return 0;
@@ -1902,7 +1958,7 @@ snd_azf3328_timer_start(struct snd_timer *timer)
        snd_azf3328_dbgtimer("setting timer countdown value %d, add COUNTDOWN|IRQ\n", delay);
        delay |= TIMER_COUNTDOWN_ENABLE | TIMER_IRQ_ENABLE;
        spin_lock_irqsave(&chip->reg_lock, flags);
-       snd_azf3328_codec_outl(chip, IDX_IO_TIMER_VALUE, delay);
+       snd_azf3328_ctrl_outl(chip, IDX_IO_TIMER_VALUE, delay);
        spin_unlock_irqrestore(&chip->reg_lock, flags);
        snd_azf3328_dbgcallleave();
        return 0;
@@ -1919,7 +1975,7 @@ snd_azf3328_timer_stop(struct snd_timer *timer)
        spin_lock_irqsave(&chip->reg_lock, flags);
        /* disable timer countdown and interrupt */
        /* FIXME: should we write TIMER_IRQ_ACK here? */
-       snd_azf3328_codec_outb(chip, IDX_IO_TIMER_VALUE + 3, 0);
+       snd_azf3328_ctrl_outb(chip, IDX_IO_TIMER_VALUE + 3, 0);
        spin_unlock_irqrestore(&chip->reg_lock, flags);
        snd_azf3328_dbgcallleave();
        return 0;
@@ -2048,9 +2104,9 @@ snd_azf3328_debug_show_ports(const struct snd_azf3328 *chip)
        u16 tmp;
 
        snd_azf3328_dbgmisc(
-               "codec_io 0x%lx, game_io 0x%lx, mpu_io 0x%lx, "
+               "ctrl_io 0x%lx, game_io 0x%lx, mpu_io 0x%lx, "
                "opl3_io 0x%lx, mixer_io 0x%lx, irq %d\n",
-               chip->codec_io, chip->game_io, chip->mpu_io,
+               chip->ctrl_io, chip->game_io, chip->mpu_io,
                chip->opl3_io, chip->mixer_io, chip->irq
        );
 
@@ -2083,9 +2139,9 @@ snd_azf3328_debug_show_ports(const struct snd_azf3328 *chip)
                                inb(0x38c + tmp)
                );
 
-       for (tmp = 0; tmp < AZF_IO_SIZE_CODEC; tmp += 2)
-               snd_azf3328_dbgmisc("codec 0x%02x: 0x%04x\n",
-                       tmp, snd_azf3328_codec_inw(chip, tmp)
+       for (tmp = 0; tmp < AZF_IO_SIZE_CTRL; tmp += 2)
+               snd_azf3328_dbgmisc("ctrl 0x%02x: 0x%04x\n",
+                       tmp, snd_azf3328_ctrl_inw(chip, tmp)
                );
 
        for (tmp = 0; tmp < AZF_IO_SIZE_MIXER; tmp += 2)
@@ -2106,7 +2162,8 @@ snd_azf3328_create(struct snd_card *card,
        static struct snd_device_ops ops = {
                .dev_free =     snd_azf3328_dev_free,
        };
-       u16 tmp;
+       u8 dma_init;
+       enum snd_azf3328_codec_type codec_type;
 
        *rchip = NULL;
 
@@ -2138,14 +2195,21 @@ snd_azf3328_create(struct snd_card *card,
        if (err < 0)
                goto out_err;
 
-       chip->codec_io = pci_resource_start(pci, 0);
+       chip->ctrl_io  = pci_resource_start(pci, 0);
        chip->game_io  = pci_resource_start(pci, 1);
        chip->mpu_io   = pci_resource_start(pci, 2);
-       chip->opl3_io = pci_resource_start(pci, 3);
+       chip->opl3_io  = pci_resource_start(pci, 3);
        chip->mixer_io = pci_resource_start(pci, 4);
 
-       chip->audio_stream[AZF_PLAYBACK].portbase = chip->codec_io + 0x00;
-       chip->audio_stream[AZF_CAPTURE].portbase   = chip->codec_io + 0x20;
+       chip->codecs[AZF_CODEC_PLAYBACK].io_base =
+                               chip->ctrl_io + AZF_IO_OFFS_CODEC_PLAYBACK;
+       chip->codecs[AZF_CODEC_PLAYBACK].name = "PLAYBACK";
+       chip->codecs[AZF_CODEC_CAPTURE].io_base =
+                               chip->ctrl_io + AZF_IO_OFFS_CODEC_CAPTURE;
+       chip->codecs[AZF_CODEC_CAPTURE].name = "CAPTURE";
+       chip->codecs[AZF_CODEC_I2S_OUT].io_base =
+                               chip->ctrl_io + AZF_IO_OFFS_CODEC_I2S_OUT;
+       chip->codecs[AZF_CODEC_I2S_OUT].name = "I2S_OUT";
 
        if (request_irq(pci->irq, snd_azf3328_interrupt,
                        IRQF_SHARED, card->shortname, chip)) {
@@ -2168,20 +2232,25 @@ snd_azf3328_create(struct snd_card *card,
        if (err < 0)
                goto out_err;
 
-       /* shutdown codecs to save power */
-               /* have snd_azf3328_codec_activity() act properly */
-       chip->audio_stream[AZF_PLAYBACK].running = 1;
-       snd_azf3328_codec_activity(chip, AZF_PLAYBACK, 0);
+       /* standard codec init stuff */
+               /* default DMA init value */
+       dma_init = DMA_RUN_SOMETHING2|DMA_EPILOGUE_SOMETHING|DMA_SOMETHING_ELSE;
+
+       for (codec_type = AZF_CODEC_PLAYBACK;
+               codec_type <= AZF_CODEC_I2S_OUT; ++codec_type) {
+               struct snd_azf3328_codec_data *codec =
+                        &chip->codecs[codec_type];
 
-       /* standard chip init stuff */
-               /* default IRQ init value */
-       tmp = DMA_PLAY_SOMETHING2|DMA_EPILOGUE_SOMETHING|DMA_SOMETHING_ELSE;
+               /* shutdown codecs to save power */
+                       /* have ...ctrl_codec_activity() act properly */
+               codec->running = 1;
+               snd_azf3328_ctrl_codec_activity(chip, codec_type, 0);
 
-       spin_lock_irq(&chip->reg_lock);
-       snd_azf3328_codec_outb(chip, IDX_IO_PLAY_FLAGS, tmp);
-       snd_azf3328_codec_outb(chip, IDX_IO_REC_FLAGS, tmp);
-       snd_azf3328_codec_outb(chip, IDX_IO_SOMETHING_FLAGS, tmp);
-       spin_unlock_irq(&chip->reg_lock);
+               spin_lock_irq(&chip->reg_lock);
+               snd_azf3328_codec_outb(codec, IDX_IO_CODEC_DMA_FLAGS,
+                                                dma_init);
+               spin_unlock_irq(&chip->reg_lock);
+       }
 
        snd_card_set_dev(card, &pci->dev);
 
@@ -2244,7 +2313,7 @@ snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
        if (err < 0)
                goto out_err;
 
-       err = snd_azf3328_pcm(chip, 0);
+       err = snd_azf3328_pcm(chip);
        if (err < 0)
                goto out_err;
 
@@ -2266,7 +2335,7 @@ snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
        opl3->private_data = chip;
 
        sprintf(card->longname, "%s at 0x%lx, irq %i",
-               card->shortname, chip->codec_io, chip->irq);
+               card->shortname, chip->ctrl_io, chip->irq);
 
        err = snd_card_register(card);
        if (err < 0)
@@ -2317,7 +2386,8 @@ snd_azf3328_suspend(struct pci_dev *pci, pm_message_t state)
 
        snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
 
-       snd_pcm_suspend_all(chip->pcm);
+       snd_pcm_suspend_all(chip->pcm[AZF_CODEC_PLAYBACK]);
+       snd_pcm_suspend_all(chip->pcm[AZF_CODEC_I2S_OUT]);
 
        for (reg = 0; reg < AZF_IO_SIZE_MIXER_PM / 2; ++reg)
                chip->saved_regs_mixer[reg] = inw(chip->mixer_io + reg * 2);
@@ -2326,11 +2396,11 @@ snd_azf3328_suspend(struct pci_dev *pci, pm_message_t state)
        snd_azf3328_mixer_set_mute(chip, IDX_MIXER_PLAY_MASTER, 1);
        snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1);
 
-       for (reg = 0; reg < AZF_IO_SIZE_CODEC_PM / 2; ++reg)
-               chip->saved_regs_codec[reg] = inw(chip->codec_io + reg * 2);
+       for (reg = 0; reg < AZF_IO_SIZE_CTRL_PM / 2; ++reg)
+               chip->saved_regs_ctrl[reg] = inw(chip->ctrl_io + reg * 2);
 
        /* manually store the one currently relevant write-only reg, too */
-       chip->saved_regs_codec[IDX_IO_6AH / 2] = chip->shadow_reg_codec_6AH;
+       chip->saved_regs_ctrl[IDX_IO_6AH / 2] = chip->shadow_reg_ctrl_6AH;
 
        for (reg = 0; reg < AZF_IO_SIZE_GAME_PM / 2; ++reg)
                chip->saved_regs_game[reg] = inw(chip->game_io + reg * 2);
@@ -2349,7 +2419,7 @@ static int
 snd_azf3328_resume(struct pci_dev *pci)
 {
        struct snd_card *card = pci_get_drvdata(pci);
-       struct snd_azf3328 *chip = card->private_data;
+       const struct snd_azf3328 *chip = card->private_data;
        unsigned reg;
 
        pci_set_power_state(pci, PCI_D0);
@@ -2370,8 +2440,8 @@ snd_azf3328_resume(struct pci_dev *pci)
                outw(chip->saved_regs_opl3[reg], chip->opl3_io + reg * 2);
        for (reg = 0; reg < AZF_IO_SIZE_MIXER_PM / 2; ++reg)
                outw(chip->saved_regs_mixer[reg], chip->mixer_io + reg * 2);
-       for (reg = 0; reg < AZF_IO_SIZE_CODEC_PM / 2; ++reg)
-               outw(chip->saved_regs_codec[reg], chip->codec_io + reg * 2);
+       for (reg = 0; reg < AZF_IO_SIZE_CTRL_PM / 2; ++reg)
+               outw(chip->saved_regs_ctrl[reg], chip->ctrl_io + reg * 2);
 
        snd_power_change_state(card, SNDRV_CTL_POWER_D0);
        return 0;
index 974e051..11d4b10 100644 (file)
@@ -6,50 +6,59 @@
 
 /*** main I/O area port indices ***/
 /* (only 0x70 of 0x80 bytes saved/restored by Windows driver) */
-#define AZF_IO_SIZE_CODEC      0x80
-#define AZF_IO_SIZE_CODEC_PM   0x70
+#define AZF_IO_SIZE_CTRL       0x80
+#define AZF_IO_SIZE_CTRL_PM    0x70
 
-/* the driver initialisation suggests a layout of 4 main areas:
- * from 0x00 (playback), from 0x20 (recording) and from 0x40 (maybe MPU401??).
+/* the driver initialisation suggests a layout of 4 areas
+ * within the main card control I/O:
+ * from 0x00 (playback codec), from 0x20 (recording codec)
+ * and from 0x40 (most certainly I2S out codec).
  * And another area from 0x60 to 0x6f (DirectX timer, IRQ management,
  * power management etc.???). */
 
-/** playback area **/
-#define IDX_IO_PLAY_FLAGS       0x00 /* PU:0x0000 */
+#define AZF_IO_OFFS_CODEC_PLAYBACK     0x00
+#define AZF_IO_OFFS_CODEC_CAPTURE      0x20
+#define AZF_IO_OFFS_CODEC_I2S_OUT      0x40
+
+#define IDX_IO_CODEC_DMA_FLAGS       0x00 /* PU:0x0000 */
      /* able to reactivate output after output muting due to 8/16bit
       * output change, just like 0x0002.
       * 0x0001 is the only bit that's able to start the DMA counter */
-  #define DMA_RESUME                   0x0001 /* paused if cleared ? */
+  #define DMA_RESUME                   0x0001 /* paused if cleared? */
      /* 0x0002 *temporarily* set during DMA stopping. hmm
       * both 0x0002 and 0x0004 set in playback setup. */
      /* able to reactivate output after output muting due to 8/16bit
       * output change, just like 0x0001. */
-  #define DMA_PLAY_SOMETHING1          0x0002 /* \ alternated (toggled) */
+  #define DMA_RUN_SOMETHING1           0x0002 /* \ alternated (toggled) */
      /* 0x0004: NOT able to reactivate output */
-  #define DMA_PLAY_SOMETHING2          0x0004 /* / bits */
+  #define DMA_RUN_SOMETHING2           0x0004 /* / bits */
   #define SOMETHING_ALMOST_ALWAYS_SET  0x0008 /* ???; can be modified */
   #define DMA_EPILOGUE_SOMETHING       0x0010
   #define DMA_SOMETHING_ELSE           0x0020 /* ??? */
-  #define SOMETHING_UNMODIFIABLE       0xffc0 /* unused ? not modifiable */
-#define IDX_IO_PLAY_IRQTYPE     0x02 /* PU:0x0001 */
+  #define SOMETHING_UNMODIFIABLE       0xffc0 /* unused? not modifiable */
+#define IDX_IO_CODEC_IRQTYPE     0x02 /* PU:0x0001 */
   /* write back to flags in case flags are set, in order to ACK IRQ in handler
    * (bit 1 of port 0x64 indicates interrupt for one of these three types)
    * sometimes in this case it just writes 0xffff to globally ACK all IRQs
    * settings written are not reflected when reading back, though.
-   * seems to be IRQ, too (frequently used: port |= 0x07 !), but who knows ? */
-  #define IRQ_PLAY_SOMETHING           0x0001 /* something & ACK */
-  #define IRQ_FINISHED_PLAYBUF_1       0x0002 /* 1st dmabuf finished & ACK */
-  #define IRQ_FINISHED_PLAYBUF_2       0x0004 /* 2nd dmabuf finished & ACK */
+   * seems to be IRQ, too (frequently used: port |= 0x07 !), but who knows? */
+  #define IRQ_SOMETHING                        0x0001 /* something & ACK */
+  #define IRQ_FINISHED_DMABUF_1                0x0002 /* 1st dmabuf finished & ACK */
+  #define IRQ_FINISHED_DMABUF_2                0x0004 /* 2nd dmabuf finished & ACK */
   #define IRQMASK_SOME_STATUS_1                0x0008 /* \ related bits */
   #define IRQMASK_SOME_STATUS_2                0x0010 /* / (checked together in loop) */
-  #define IRQMASK_UNMODIFIABLE         0xffe0 /* unused ? not modifiable */
-#define IDX_IO_PLAY_DMA_START_1 0x04 /* start address of 1st DMA play area, PU:0x00000000 */
-#define IDX_IO_PLAY_DMA_START_2 0x08 /* start address of 2nd DMA play area, PU:0x00000000 */
-#define IDX_IO_PLAY_DMA_LEN_1   0x0c /* length of 1st DMA play area, PU:0x0000 */
-#define IDX_IO_PLAY_DMA_LEN_2   0x0e /* length of 2nd DMA play area, PU:0x0000 */
-#define IDX_IO_PLAY_DMA_CURRPOS 0x10 /* current DMA position, PU:0x00000000 */
-#define IDX_IO_PLAY_DMA_CURROFS        0x14 /* offset within current DMA play area, PU:0x0000 */
-#define IDX_IO_PLAY_SOUNDFORMAT 0x16 /* PU:0x0010 */
+  #define IRQMASK_UNMODIFIABLE         0xffe0 /* unused? not modifiable */
+  /* start address of 1st DMA transfer area, PU:0x00000000 */
+#define IDX_IO_CODEC_DMA_START_1 0x04
+  /* start address of 2nd DMA transfer area, PU:0x00000000 */
+#define IDX_IO_CODEC_DMA_START_2 0x08
+  /* both lengths of DMA transfer areas, PU:0x00000000
+     length1: offset 0x0c, length2: offset 0x0e */
+#define IDX_IO_CODEC_DMA_LENGTHS 0x0c
+#define IDX_IO_CODEC_DMA_CURRPOS 0x10 /* current DMA position, PU:0x00000000 */
+  /* offset within current DMA transfer area, PU:0x0000 */
+#define IDX_IO_CODEC_DMA_CURROFS 0x14
+#define IDX_IO_CODEC_SOUNDFORMAT 0x16 /* PU:0x0010 */
   /* all unspecified bits can't be modified */
   #define SOUNDFORMAT_FREQUENCY_MASK   0x000f
   #define SOUNDFORMAT_XTAL1            0x00
@@ -76,6 +85,7 @@
   #define SOUNDFORMAT_FLAG_16BIT       0x0010
   #define SOUNDFORMAT_FLAG_2CHANNELS   0x0020
 
+
 /* define frequency helpers, for maximum value safety */
 enum azf_freq_t {
 #define AZF_FREQ(rate) AZF_FREQ_##rate = rate
@@ -96,29 +106,6 @@ enum azf_freq_t {
 #undef AZF_FREQ
 };
 
-/** recording area (see also: playback bit flag definitions) **/
-#define IDX_IO_REC_FLAGS       0x20 /* ??, PU:0x0000 */
-#define IDX_IO_REC_IRQTYPE     0x22 /* ??, PU:0x0000 */
-  #define IRQ_REC_SOMETHING            0x0001 /* something & ACK */
-  #define IRQ_FINISHED_RECBUF_1                0x0002 /* 1st dmabuf finished & ACK */
-  #define IRQ_FINISHED_RECBUF_2                0x0004 /* 2nd dmabuf finished & ACK */
-  /* hmm, maybe these are just the corresponding *recording* flags ?
-   * but OTOH they are most likely at port 0x22 instead */
-  #define IRQMASK_SOME_STATUS_1                0x0008 /* \ related bits */
-  #define IRQMASK_SOME_STATUS_2                0x0010 /* / (checked together in loop) */
-#define IDX_IO_REC_DMA_START_1  0x24 /* PU:0x00000000 */
-#define IDX_IO_REC_DMA_START_2  0x28 /* PU:0x00000000 */
-#define IDX_IO_REC_DMA_LEN_1    0x2c /* PU:0x0000 */
-#define IDX_IO_REC_DMA_LEN_2    0x2e /* PU:0x0000 */
-#define IDX_IO_REC_DMA_CURRPOS  0x30 /* PU:0x00000000 */
-#define IDX_IO_REC_DMA_CURROFS  0x34 /* PU:0x00000000 */
-#define IDX_IO_REC_SOUNDFORMAT  0x36 /* PU:0x0000 */
-
-/** hmm, what is this I/O area for? MPU401?? or external DAC via I2S?? (after playback, recording, ???, timer) **/
-#define IDX_IO_SOMETHING_FLAGS 0x40 /* gets set to 0x34 just like port 0x0 and 0x20 on card init, PU:0x0000 */
-/* general */
-#define IDX_IO_42H             0x42 /* PU:0x0001 */
-
 /** DirectX timer, main interrupt area (FIXME: and something else?) **/ 
 #define IDX_IO_TIMER_VALUE     0x60 /* found this timer area by pure luck :-) */
   /* timer countdown value; triggers IRQ when timer is finished */
@@ -138,7 +125,7 @@ enum azf_freq_t {
 
   #define IRQ_PLAYBACK 0x0001
   #define IRQ_RECORDING        0x0002
-  #define IRQ_UNKNOWN1 0x0004 /* most probably I2S port */
+  #define IRQ_I2S_OUT  0x0004 /* this IS I2S, right!? (untested) */
   #define IRQ_GAMEPORT 0x0008 /* Interrupt of Digital(ly) Enhanced Game Port */
   #define IRQ_MPU401   0x0010
   #define IRQ_TIMER    0x0020 /* DirectX timer */
@@ -272,6 +259,12 @@ enum {
    * 11 --> 1/200: */
   #define GAME_HWCFG_ADC_COUNTER_FREQ_MASK     0x06
 
+  /* FIXME: these values might be reversed... */
+  #define GAME_HWCFG_ADC_COUNTER_FREQ_STD      0
+  #define GAME_HWCFG_ADC_COUNTER_FREQ_1_2      1
+  #define GAME_HWCFG_ADC_COUNTER_FREQ_1_20     2
+  #define GAME_HWCFG_ADC_COUNTER_FREQ_1_200    3
+
   /* enable gameport legacy I/O address (0x200)
    * I was unable to locate any configurability for a different address: */
   #define GAME_HWCFG_LEGACY_ADDRESS_ENABLE     0x08