]> err.no Git - linux-2.6/commitdiff
[ALSA] add CMI8788 driver
authorClemens Ladisch <clemens@ladisch.de>
Sun, 23 Dec 2007 18:50:57 +0000 (19:50 +0100)
committerJaroslav Kysela <perex@perex.cz>
Thu, 31 Jan 2008 16:29:44 +0000 (17:29 +0100)
Add the snd-oxygen driver for the C-Media CMI8788 (Oxygen) chip, used on
the Asound A-8788, AuzenTech X-Meridian, Bgears b-Enspirer,
Club3D Theatron DTS, HT-Omega Claro, Razer Barracuda AC-1,
Sondigo Inferno, and TempoTec HIFIER sound cards.

Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
sound/pci/Kconfig
sound/pci/Makefile
sound/pci/oxygen/Makefile [new file with mode: 0644]
sound/pci/oxygen/oxygen.c [new file with mode: 0644]
sound/pci/oxygen/oxygen.h [new file with mode: 0644]
sound/pci/oxygen/oxygen_io.c [new file with mode: 0644]
sound/pci/oxygen/oxygen_lib.c [new file with mode: 0644]
sound/pci/oxygen/oxygen_mixer.c [new file with mode: 0644]
sound/pci/oxygen/oxygen_pcm.c [new file with mode: 0644]
sound/pci/oxygen/oxygen_regs.h [new file with mode: 0644]

index 45f0f6c2f353a09abbb1346210331701d16ce3eb..d3be87d11259f9b5802e8de9024fff5fa5aad342 100644 (file)
@@ -183,6 +183,31 @@ config SND_CMIPCI
          To compile this driver as a module, choose M here: the module
          will be called snd-cmipci.
 
+config SND_OXYGEN_LIB
+        tristate
+       depends on SND
+       select SND_PCM
+       select SND_MPU401_UART
+
+config SND_OXYGEN
+       tristate "C-Media 8788 (Oxygen)"
+       depends on SND
+       select SND_OXYGEN_LIB
+       help
+         Say Y here to include support for sound cards based on the
+         C-Media CMI8788 (Oxygen HD Audio) chip:
+          * Asound A-8788
+          * AuzenTech X-Meridian
+          * Bgears b-Enspirer
+          * Club3D Theatron DTS
+          * HT-Omega Claro
+          * Razer Barracuda AC-1
+          * Sondigo Inferno
+          * TempoTec HIFIER
+
+         To compile this driver as a module, choose M here: the module
+         will be called snd-oxygen.
+
 config SND_CS4281
        tristate "Cirrus Logic (Sound Fusion) CS4281"
        depends on SND
index 56738da9c14ae770f09155758a22c8f0e2efc63b..2d42fd28f4e7950305d80d930569dfd759818b56 100644 (file)
@@ -68,6 +68,7 @@ obj-$(CONFIG_SND) += \
        korg1212/ \
        mixart/ \
        nm256/ \
+       oxygen/ \
        pcxhr/ \
        riptide/ \
        rme9652/ \
diff --git a/sound/pci/oxygen/Makefile b/sound/pci/oxygen/Makefile
new file mode 100644 (file)
index 0000000..99455dd
--- /dev/null
@@ -0,0 +1,5 @@
+snd-oxygen-lib-objs := oxygen_io.o oxygen_lib.o oxygen_mixer.o oxygen_pcm.o
+snd-oxygen-objs := oxygen.o
+
+obj-$(CONFIG_SND_OXYGEN_LIB) += snd-oxygen-lib.o
+obj-$(CONFIG_SND_OXYGEN) += snd-oxygen.o
diff --git a/sound/pci/oxygen/oxygen.c b/sound/pci/oxygen/oxygen.c
new file mode 100644 (file)
index 0000000..bfef5ab
--- /dev/null
@@ -0,0 +1,314 @@
+/*
+ * C-Media CMI8788 driver for C-Media's reference design and for the X-Meridian
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this driver; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+/*
+ * SPI 0 -> 1st AK4396 (front)
+ * SPI 1 -> 2nd AK4396 (side)
+ * SPI 2 -> 3rd AK4396 (center/LFE)
+ * SPI 3 -> WM8785
+ * SPI 4 -> 4th AK4396 (rear)
+ *
+ * GPIO 0 -> DFS0 of AK5385
+ * GPIO 1 -> DFS1 of AK5385
+ */
+
+#include <sound/driver.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include "oxygen.h"
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("C-Media CMI8788 driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{C-Media,CMI8788}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "card index");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "enable card");
+
+static struct pci_device_id oxygen_ids[] __devinitdata = {
+       { OXYGEN_PCI_SUBID(0x10b0, 0x0216) },
+       { OXYGEN_PCI_SUBID(0x10b0, 0x0218) },
+       { OXYGEN_PCI_SUBID(0x10b0, 0x0219) },
+       { OXYGEN_PCI_SUBID(0x13f6, 0x0001) },
+       { OXYGEN_PCI_SUBID(0x13f6, 0x0010) },
+       { OXYGEN_PCI_SUBID(0x13f6, 0x8788) },
+       { OXYGEN_PCI_SUBID(0x147a, 0xa017) },
+       { OXYGEN_PCI_SUBID(0x14c3, 0x1710) },
+       { OXYGEN_PCI_SUBID(0x14c3, 0x1711) },
+       { OXYGEN_PCI_SUBID(0x1a58, 0x0910) },
+       { OXYGEN_PCI_SUBID(0x415a, 0x5431), .driver_data = 1 },
+       { OXYGEN_PCI_SUBID(0x7284, 0x9761) },
+       { }
+};
+MODULE_DEVICE_TABLE(pci, oxygen_ids);
+
+#define AK4396_WRITE   0x2000
+
+/* register 0 */
+#define AK4396_RSTN            0x01
+#define AK4396_DIF_24_MSB      0x04
+/* register 1 */
+#define AK4396_SMUTE           0x01
+#define AK4396_DEM_OFF         0x02
+#define AK4396_DFS_MASK                0x18
+#define AK4396_DFS_NORMAL      0x00
+#define AK4396_DFS_DOUBLE      0x08
+#define AK4396_DFS_QUAD                0x10
+
+/* register 0 */
+#define WM8785_OSR_SINGLE      0x000
+#define WM8785_OSR_DOUBLE      0x008
+#define WM8785_OSR_QUAD                0x010
+#define WM8785_FORMAT_LJUST    0x020
+#define WM8785_FORMAT_I2S      0x040
+/* register 1 */
+#define WM8785_WL_16           0x000
+#define WM8785_WL_20           0x001
+#define WM8785_WL_24           0x002
+#define WM8785_WL_32           0x003
+
+static void ak4396_write(struct oxygen *chip, unsigned int codec,
+                        u8 reg, u8 value)
+{
+       /* maps ALSA channel pair number to SPI output */
+       static const u8 codec_spi_map[4] = {
+               0, 4, 2, 1
+       };
+       oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER_WRITE |
+                        OXYGEN_SPI_DATA_LENGTH_2 |
+                        (codec_spi_map[codec] << OXYGEN_SPI_CODEC_SHIFT) |
+                        OXYGEN_SPI_MAGIC,
+                        AK4396_WRITE | (reg << 8) | value);
+}
+
+static void wm8785_write(struct oxygen *chip, u8 reg, unsigned int value)
+{
+       oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER_WRITE |
+                        OXYGEN_SPI_DATA_LENGTH_2 |
+                        (3 << OXYGEN_SPI_CODEC_SHIFT),
+                        (reg << 9) | value);
+}
+
+static void ak4396_init(struct oxygen *chip)
+{
+       unsigned int i;
+
+       chip->ak4396_reg1 = AK4396_DEM_OFF | AK4396_DFS_NORMAL;
+       for (i = 0; i < 4; ++i) {
+               ak4396_write(chip, i, 0, AK4396_DIF_24_MSB | AK4396_RSTN);
+               ak4396_write(chip, i, 1, chip->ak4396_reg1);
+               ak4396_write(chip, i, 2, 0);
+               ak4396_write(chip, i, 3, 0xff);
+               ak4396_write(chip, i, 4, 0xff);
+       }
+       snd_component_add(chip->card, "AK4396");
+}
+
+static void ak5385_init(struct oxygen *chip)
+{
+       oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, 0x0003);
+       oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, 0x0003);
+       snd_component_add(chip->card, "AK5385");
+}
+
+static void wm8785_init(struct oxygen *chip)
+{
+       wm8785_write(chip, 7, 0);
+       wm8785_write(chip, 0, WM8785_FORMAT_LJUST | WM8785_OSR_SINGLE);
+       wm8785_write(chip, 1, WM8785_WL_24);
+       snd_component_add(chip->card, "WM8785");
+}
+
+static void generic_init(struct oxygen *chip)
+{
+       ak4396_init(chip);
+       wm8785_init(chip);
+}
+
+static void meridian_init(struct oxygen *chip)
+{
+       ak4396_init(chip);
+       ak5385_init(chip);
+}
+
+static void generic_cleanup(struct oxygen *chip)
+{
+}
+
+static void set_ak4396_params(struct oxygen *chip,
+                             struct snd_pcm_hw_params *params)
+{
+       unsigned int i;
+       u8 value;
+
+       value = chip->ak4396_reg1 & ~AK4396_DFS_MASK;
+       if (params_rate(params) <= 54000)
+               value |= AK4396_DFS_NORMAL;
+       else if (params_rate(params) < 120000)
+               value |= AK4396_DFS_DOUBLE;
+       else
+               value |= AK4396_DFS_QUAD;
+       chip->ak4396_reg1 = value;
+       for (i = 0; i < 4; ++i) {
+               ak4396_write(chip, i, 0, AK4396_DIF_24_MSB);
+               ak4396_write(chip, i, 1, value);
+               ak4396_write(chip, i, 0, AK4396_DIF_24_MSB | AK4396_RSTN);
+       }
+}
+
+static void update_ak4396_volume(struct oxygen *chip)
+{
+       unsigned int i;
+
+       for (i = 0; i < 4; ++i) {
+               ak4396_write(chip, i, 3, chip->dac_volume[i * 2]);
+               ak4396_write(chip, i, 4, chip->dac_volume[i * 2 + 1]);
+       }
+}
+
+static void update_ak4396_mute(struct oxygen *chip)
+{
+       unsigned int i;
+       u8 value;
+
+       value = chip->ak4396_reg1 & ~AK4396_SMUTE;
+       if (chip->dac_mute)
+               value |= AK4396_SMUTE;
+       for (i = 0; i < 4; ++i)
+               ak4396_write(chip, i, 1, value);
+}
+
+static void set_wm8785_params(struct oxygen *chip,
+                             struct snd_pcm_hw_params *params)
+{
+       unsigned int value;
+
+       wm8785_write(chip, 7, 0);
+
+       value = WM8785_FORMAT_LJUST;
+       if (params_rate(params) == 96000)
+               value |= WM8785_OSR_DOUBLE;
+       else if (params_rate(params) == 192000)
+               value |= WM8785_OSR_QUAD;
+       else
+               value |= WM8785_OSR_SINGLE;
+       wm8785_write(chip, 0, value);
+
+       if (snd_pcm_format_width(params_format(params)) <= 16)
+               value = WM8785_WL_16;
+       else
+               value = WM8785_WL_24;
+       wm8785_write(chip, 1, value);
+}
+
+static void set_ak5385_params(struct oxygen *chip,
+                             struct snd_pcm_hw_params *params)
+{
+       unsigned int value;
+
+       if (params_rate(params) <= 54000)
+               value = 0;
+       else if (params_rate(params) <= 108000)
+               value = 1;
+       else
+               value = 2;
+       oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, value, 0x0003);
+}
+
+static const DECLARE_TLV_DB_LINEAR(ak4396_db_scale, TLV_DB_GAIN_MUTE, 0);
+
+static const struct oxygen_model model_generic = {
+       .shortname = "C-Media CMI8788",
+       .longname = "C-Media Oxygen HD Audio",
+       .chip = "CMI8788",
+       .owner = THIS_MODULE,
+       .init = generic_init,
+       .cleanup = generic_cleanup,
+       .set_dac_params = set_ak4396_params,
+       .set_adc_params = set_wm8785_params,
+       .update_dac_volume = update_ak4396_volume,
+       .update_dac_mute = update_ak4396_mute,
+       .dac_tlv = ak4396_db_scale,
+};
+static const struct oxygen_model model_meridian = {
+       .shortname = "C-Media CMI8788",
+       .longname = "C-Media Oxygen HD Audio",
+       .chip = "CMI8788",
+       .owner = THIS_MODULE,
+       .init = meridian_init,
+       .cleanup = generic_cleanup,
+       .set_dac_params = set_ak4396_params,
+       .set_adc_params = set_ak5385_params,
+       .update_dac_volume = update_ak4396_volume,
+       .update_dac_mute = update_ak4396_mute,
+       .dac_tlv = ak4396_db_scale,
+       .record_from_dma_b = 1,
+};
+
+static int __devinit generic_oxygen_probe(struct pci_dev *pci,
+                                         const struct pci_device_id *pci_id)
+{
+       static int dev;
+       const struct oxygen_model *model;
+       int err;
+
+       if (dev >= SNDRV_CARDS)
+               return -ENODEV;
+       if (!enable[dev]) {
+               ++dev;
+               return -ENOENT;
+       }
+       model = pci_id->driver_data ? &model_meridian : &model_generic;
+       err = oxygen_pci_probe(pci, index[dev], id[dev], model);
+       if (err >= 0)
+               ++dev;
+       return err;
+}
+
+static struct pci_driver oxygen_driver = {
+       .name = "CMI8788",
+       .id_table = oxygen_ids,
+       .probe = generic_oxygen_probe,
+       .remove = __devexit_p(oxygen_pci_remove),
+};
+
+static int __init alsa_card_oxygen_init(void)
+{
+       return pci_register_driver(&oxygen_driver);
+}
+
+static void __exit alsa_card_oxygen_exit(void)
+{
+       pci_unregister_driver(&oxygen_driver);
+}
+
+module_init(alsa_card_oxygen_init)
+module_exit(alsa_card_oxygen_exit)
diff --git a/sound/pci/oxygen/oxygen.h b/sound/pci/oxygen/oxygen.h
new file mode 100644 (file)
index 0000000..248f7ed
--- /dev/null
@@ -0,0 +1,167 @@
+#ifndef OXYGEN_H_INCLUDED
+#define OXYGEN_H_INCLUDED
+
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include "oxygen_regs.h"
+
+/* 1 << PCM_x == OXYGEN_CHANNEL_x */
+#define PCM_A          0
+#define PCM_B          1
+#define PCM_C          2
+#define PCM_SPDIF      3
+#define PCM_MULTICH    4
+#define PCM_AC97       5
+#define PCM_COUNT      6
+
+#define OXYGEN_PCI_SUBID(sv, sd) \
+       .vendor = PCI_VENDOR_ID_CMEDIA, \
+       .device = 0x8788, \
+       .subvendor = sv, \
+       .subdevice = sd
+
+struct pci_dev;
+struct snd_card;
+struct snd_pcm_substream;
+struct snd_pcm_hw_params;
+struct snd_rawmidi;
+struct oxygen_model;
+
+struct oxygen {
+       unsigned long addr;
+       spinlock_t reg_lock;
+       struct mutex mutex;
+       struct snd_card *card;
+       struct pci_dev *pci;
+       struct snd_rawmidi *midi;
+       int irq;
+       const struct oxygen_model *model;
+       unsigned int interrupt_mask;
+       u8 dac_volume[8];
+       u8 dac_mute;
+       u8 pcm_active;
+       u8 pcm_running;
+       u8 dac_routing;
+       u8 spdif_playback_enable;
+       u8 ak4396_reg1;
+       u8 revision;
+       u8 has_2nd_ac97_codec;
+       u32 spdif_bits;
+       u32 spdif_pcm_bits;
+       struct snd_pcm_substream *streams[PCM_COUNT];
+       struct snd_kcontrol *spdif_pcm_ctl;
+       struct snd_kcontrol *spdif_input_bits_ctl;
+       struct work_struct spdif_input_bits_work;
+};
+
+struct oxygen_model {
+       const char *shortname;
+       const char *longname;
+       const char *chip;
+       struct module *owner;
+       void (*init)(struct oxygen *chip);
+       int (*mixer_init)(struct oxygen *chip);
+       void (*cleanup)(struct oxygen *chip);
+       void (*set_dac_params)(struct oxygen *chip,
+                              struct snd_pcm_hw_params *params);
+       void (*set_adc_params)(struct oxygen *chip,
+                              struct snd_pcm_hw_params *params);
+       void (*update_dac_volume)(struct oxygen *chip);
+       void (*update_dac_mute)(struct oxygen *chip);
+       const unsigned int *dac_tlv;
+       u8 record_from_dma_b;
+       u8 cd_in_from_video_in;
+       u8 dac_minimum_volume;
+};
+
+/* oxygen_lib.c */
+
+int oxygen_pci_probe(struct pci_dev *pci, int index, char *id,
+                    const struct oxygen_model *model);
+void oxygen_pci_remove(struct pci_dev *pci);
+
+/* oxygen_mixer.c */
+
+int oxygen_mixer_init(struct oxygen *chip);
+void oxygen_update_dac_routing(struct oxygen *chip);
+void oxygen_update_spdif_source(struct oxygen *chip);
+
+/* oxygen_pcm.c */
+
+int oxygen_pcm_init(struct oxygen *chip);
+
+/* oxygen_io.c */
+
+u8 oxygen_read8(struct oxygen *chip, unsigned int reg);
+u16 oxygen_read16(struct oxygen *chip, unsigned int reg);
+u32 oxygen_read32(struct oxygen *chip, unsigned int reg);
+void oxygen_write8(struct oxygen *chip, unsigned int reg, u8 value);
+void oxygen_write16(struct oxygen *chip, unsigned int reg, u16 value);
+void oxygen_write32(struct oxygen *chip, unsigned int reg, u32 value);
+void oxygen_write8_masked(struct oxygen *chip, unsigned int reg,
+                         u8 value, u8 mask);
+void oxygen_write16_masked(struct oxygen *chip, unsigned int reg,
+                          u16 value, u16 mask);
+void oxygen_write32_masked(struct oxygen *chip, unsigned int reg,
+                          u32 value, u32 mask);
+
+u16 oxygen_read_ac97(struct oxygen *chip, unsigned int codec,
+                    unsigned int index);
+void oxygen_write_ac97(struct oxygen *chip, unsigned int codec,
+                      unsigned int index, u16 data);
+void oxygen_write_ac97_masked(struct oxygen *chip, unsigned int codec,
+                             unsigned int index, u16 data, u16 mask);
+
+void oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data);
+
+static inline void oxygen_set_bits8(struct oxygen *chip,
+                                   unsigned int reg, u8 value)
+{
+       oxygen_write8_masked(chip, reg, value, value);
+}
+
+static inline void oxygen_set_bits16(struct oxygen *chip,
+                                    unsigned int reg, u16 value)
+{
+       oxygen_write16_masked(chip, reg, value, value);
+}
+
+static inline void oxygen_set_bits32(struct oxygen *chip,
+                                    unsigned int reg, u32 value)
+{
+       oxygen_write32_masked(chip, reg, value, value);
+}
+
+static inline void oxygen_clear_bits8(struct oxygen *chip,
+                                     unsigned int reg, u8 value)
+{
+       oxygen_write8_masked(chip, reg, 0, value);
+}
+
+static inline void oxygen_clear_bits16(struct oxygen *chip,
+                                      unsigned int reg, u16 value)
+{
+       oxygen_write16_masked(chip, reg, 0, value);
+}
+
+static inline void oxygen_clear_bits32(struct oxygen *chip,
+                                      unsigned int reg, u32 value)
+{
+       oxygen_write32_masked(chip, reg, 0, value);
+}
+
+static inline void oxygen_ac97_set_bits(struct oxygen *chip, unsigned int codec,
+                                       unsigned int index, u16 value)
+{
+       oxygen_write_ac97_masked(chip, codec, index, value, value);
+}
+
+static inline void oxygen_ac97_clear_bits(struct oxygen *chip,
+                                         unsigned int codec,
+                                         unsigned int index, u16 value)
+{
+       oxygen_write_ac97_masked(chip, codec, index, 0, value);
+}
+
+#endif
diff --git a/sound/pci/oxygen/oxygen_io.c b/sound/pci/oxygen/oxygen_io.c
new file mode 100644 (file)
index 0000000..5f4feea
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * C-Media CMI8788 driver - helper functions
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this driver; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/driver.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <sound/core.h>
+#include <asm/io.h>
+#include "oxygen.h"
+
+u8 oxygen_read8(struct oxygen *chip, unsigned int reg)
+{
+       return inb(chip->addr + reg);
+}
+EXPORT_SYMBOL(oxygen_read8);
+
+u16 oxygen_read16(struct oxygen *chip, unsigned int reg)
+{
+       return inw(chip->addr + reg);
+}
+EXPORT_SYMBOL(oxygen_read16);
+
+u32 oxygen_read32(struct oxygen *chip, unsigned int reg)
+{
+       return inl(chip->addr + reg);
+}
+EXPORT_SYMBOL(oxygen_read32);
+
+void oxygen_write8(struct oxygen *chip, unsigned int reg, u8 value)
+{
+       outb(value, chip->addr + reg);
+}
+EXPORT_SYMBOL(oxygen_write8);
+
+void oxygen_write16(struct oxygen *chip, unsigned int reg, u16 value)
+{
+       outw(value, chip->addr + reg);
+}
+EXPORT_SYMBOL(oxygen_write16);
+
+void oxygen_write32(struct oxygen *chip, unsigned int reg, u32 value)
+{
+       outl(value, chip->addr + reg);
+}
+EXPORT_SYMBOL(oxygen_write32);
+
+void oxygen_write8_masked(struct oxygen *chip, unsigned int reg,
+                         u8 value, u8 mask)
+{
+       u8 tmp = inb(chip->addr + reg);
+       outb((tmp & ~mask) | (value & mask), chip->addr + reg);
+}
+EXPORT_SYMBOL(oxygen_write8_masked);
+
+void oxygen_write16_masked(struct oxygen *chip, unsigned int reg,
+                          u16 value, u16 mask)
+{
+       u16 tmp = inw(chip->addr + reg);
+       outw((tmp & ~mask) | (value & mask), chip->addr + reg);
+}
+EXPORT_SYMBOL(oxygen_write16_masked);
+
+void oxygen_write32_masked(struct oxygen *chip, unsigned int reg,
+                          u32 value, u32 mask)
+{
+       u32 tmp = inl(chip->addr + reg);
+       outl((tmp & ~mask) | (value & mask), chip->addr + reg);
+}
+EXPORT_SYMBOL(oxygen_write32_masked);
+
+static int oxygen_ac97_wait(struct oxygen *chip, unsigned int mask)
+{
+       unsigned long timeout = jiffies + msecs_to_jiffies(1);
+       do {
+               udelay(5);
+               cond_resched();
+               if (oxygen_read8(chip, OXYGEN_AC97_INTERRUPT_STATUS) & mask)
+                       return 0;
+       } while (time_after_eq(timeout, jiffies));
+       return -EIO;
+}
+
+/*
+ * About 10% of AC'97 register reads or writes fail to complete, but even those
+ * where the controller indicates completion aren't guaranteed to have actually
+ * happened.
+ *
+ * It's hard to assign blame to either the controller or the codec because both
+ * were made by C-Media ...
+ */
+
+void oxygen_write_ac97(struct oxygen *chip, unsigned int codec,
+                      unsigned int index, u16 data)
+{
+       unsigned int count, succeeded;
+       u32 reg;
+
+       reg = data;
+       reg |= index << OXYGEN_AC97_REG_ADDR_SHIFT;
+       reg |= OXYGEN_AC97_REG_DIR_WRITE;
+       reg |= codec << OXYGEN_AC97_REG_CODEC_SHIFT;
+       succeeded = 0;
+       for (count = 5; count > 0; --count) {
+               udelay(5);
+               oxygen_write32(chip, OXYGEN_AC97_REGS, reg);
+               /* require two "completed" writes, just to be sure */
+               if (oxygen_ac97_wait(chip, OXYGEN_AC97_WRITE_COMPLETE) >= 0 &&
+                   ++succeeded >= 2)
+                       return;
+       }
+       snd_printk(KERN_ERR "AC'97 write timeout\n");
+}
+EXPORT_SYMBOL(oxygen_write_ac97);
+
+u16 oxygen_read_ac97(struct oxygen *chip, unsigned int codec,
+                    unsigned int index)
+{
+       unsigned int count;
+       unsigned int last_read = UINT_MAX;
+       u32 reg;
+
+       reg = index << OXYGEN_AC97_REG_ADDR_SHIFT;
+       reg |= OXYGEN_AC97_REG_DIR_READ;
+       reg |= codec << OXYGEN_AC97_REG_CODEC_SHIFT;
+       for (count = 5; count > 0; --count) {
+               udelay(5);
+               oxygen_write32(chip, OXYGEN_AC97_REGS, reg);
+               udelay(10);
+               if (oxygen_ac97_wait(chip, OXYGEN_AC97_READ_COMPLETE) >= 0) {
+                       u16 value = oxygen_read16(chip, OXYGEN_AC97_REGS);
+                       /* we require two consecutive reads of the same value */
+                       if (value == last_read)
+                               return value;
+                       last_read = value;
+                       /*
+                        * Invert the register value bits to make sure that two
+                        * consecutive unsuccessful reads do not return the same
+                        * value.
+                        */
+                       reg ^= 0xffff;
+               }
+       }
+       snd_printk(KERN_ERR "AC'97 read timeout on codec %u\n", codec);
+       return 0;
+}
+EXPORT_SYMBOL(oxygen_read_ac97);
+
+void oxygen_write_ac97_masked(struct oxygen *chip, unsigned int codec,
+                             unsigned int index, u16 data, u16 mask)
+{
+       u16 value = oxygen_read_ac97(chip, codec, index);
+       value &= ~mask;
+       value |= data & mask;
+       oxygen_write_ac97(chip, codec, index, value);
+}
+EXPORT_SYMBOL(oxygen_write_ac97_masked);
+
+void oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data)
+{
+       unsigned int count;
+
+       /* should not need more than 3.84 us (24 * 160 ns) */
+       count = 10;
+       while ((oxygen_read8(chip, OXYGEN_SPI_CONTROL) & OXYGEN_SPI_BUSY)
+              && count > 0) {
+               udelay(1);
+               --count;
+       }
+
+       spin_lock_irq(&chip->reg_lock);
+       oxygen_write8(chip, OXYGEN_SPI_DATA1, data);
+       oxygen_write8(chip, OXYGEN_SPI_DATA2, data >> 8);
+       if (control & OXYGEN_SPI_DATA_LENGTH_3)
+               oxygen_write8(chip, OXYGEN_SPI_DATA3, data >> 16);
+       oxygen_write8(chip, OXYGEN_SPI_CONTROL, control);
+       spin_unlock_irq(&chip->reg_lock);
+}
+EXPORT_SYMBOL(oxygen_write_spi);
diff --git a/sound/pci/oxygen/oxygen_lib.c b/sound/pci/oxygen/oxygen_lib.c
new file mode 100644 (file)
index 0000000..6180cc8
--- /dev/null
@@ -0,0 +1,361 @@
+/*
+ * C-Media CMI8788 driver - main driver module
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this driver; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/driver.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <sound/ac97_codec.h>
+#include <sound/asoundef.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/mpu401.h>
+#include <sound/pcm.h>
+#include "oxygen.h"
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("C-Media CMI8788 helper library");
+MODULE_LICENSE("GPL");
+
+
+static irqreturn_t oxygen_interrupt(int dummy, void *dev_id)
+{
+       struct oxygen *chip = dev_id;
+       unsigned int status, clear, elapsed_streams, i;
+
+       status = oxygen_read16(chip, OXYGEN_INTERRUPT_STATUS);
+       if (!status)
+               return IRQ_NONE;
+
+       spin_lock(&chip->reg_lock);
+
+       clear = status & (OXYGEN_CHANNEL_A |
+                         OXYGEN_CHANNEL_B |
+                         OXYGEN_CHANNEL_C |
+                         OXYGEN_CHANNEL_SPDIF |
+                         OXYGEN_CHANNEL_MULTICH |
+                         OXYGEN_CHANNEL_AC97 |
+                         OXYGEN_INT_SPDIF_IN_CHANGE |
+                         OXYGEN_INT_GPIO);
+       if (clear) {
+               if (clear & OXYGEN_INT_SPDIF_IN_CHANGE)
+                       chip->interrupt_mask &= ~OXYGEN_INT_SPDIF_IN_CHANGE;
+               oxygen_write16(chip, OXYGEN_INTERRUPT_MASK,
+                              chip->interrupt_mask & ~clear);
+               oxygen_write16(chip, OXYGEN_INTERRUPT_MASK,
+                              chip->interrupt_mask);
+       }
+
+       elapsed_streams = status & chip->pcm_running;
+
+       spin_unlock(&chip->reg_lock);
+
+       for (i = 0; i < PCM_COUNT; ++i)
+               if ((elapsed_streams & (1 << i)) && chip->streams[i])
+                       snd_pcm_period_elapsed(chip->streams[i]);
+
+       if (status & OXYGEN_INT_SPDIF_IN_CHANGE) {
+               spin_lock(&chip->reg_lock);
+               i = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL);
+               if (i & OXYGEN_SPDIF_IN_CHANGE) {
+                       oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, i);
+                       schedule_work(&chip->spdif_input_bits_work);
+               }
+               spin_unlock(&chip->reg_lock);
+       }
+
+       if (status & OXYGEN_INT_GPIO)
+               ;
+
+       if ((status & OXYGEN_INT_MIDI) && chip->midi)
+               snd_mpu401_uart_interrupt(0, chip->midi->private_data);
+
+       return IRQ_HANDLED;
+}
+
+static void oxygen_spdif_input_bits_changed(struct work_struct *work)
+{
+       struct oxygen *chip = container_of(work, struct oxygen,
+                                          spdif_input_bits_work);
+
+       spin_lock_irq(&chip->reg_lock);
+       oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL, OXYGEN_SPDIF_IN_INVERT);
+       spin_unlock_irq(&chip->reg_lock);
+       msleep(1);
+       if (!(oxygen_read32(chip, OXYGEN_SPDIF_CONTROL)
+             & OXYGEN_SPDIF_IN_VALID)) {
+               spin_lock_irq(&chip->reg_lock);
+               oxygen_set_bits32(chip, OXYGEN_SPDIF_CONTROL,
+                                 OXYGEN_SPDIF_IN_INVERT);
+               spin_unlock_irq(&chip->reg_lock);
+               msleep(1);
+               if (!(oxygen_read32(chip, OXYGEN_SPDIF_CONTROL)
+                     & OXYGEN_SPDIF_IN_VALID)) {
+                       spin_lock_irq(&chip->reg_lock);
+                       oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL,
+                                           OXYGEN_SPDIF_IN_INVERT);
+                       spin_unlock_irq(&chip->reg_lock);
+               }
+       }
+
+       if (chip->spdif_input_bits_ctl) {
+               spin_lock_irq(&chip->reg_lock);
+               chip->interrupt_mask |= OXYGEN_INT_SPDIF_IN_CHANGE;
+               oxygen_write16(chip, OXYGEN_INTERRUPT_MASK,
+                              chip->interrupt_mask);
+               spin_unlock_irq(&chip->reg_lock);
+
+               snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+                              &chip->spdif_input_bits_ctl->id);
+       }
+}
+
+#ifdef CONFIG_PROC_FS
+static void oxygen_proc_read(struct snd_info_entry *entry,
+                            struct snd_info_buffer *buffer)
+{
+       struct oxygen *chip = entry->private_data;
+       int i, j;
+
+       snd_iprintf(buffer, "CMI8788\n\n");
+       for (i = 0; i < 0x100; i += 0x10) {
+               snd_iprintf(buffer, "%02x:", i);
+               for (j = 0; j < 0x10; ++j)
+                       snd_iprintf(buffer, " %02x", oxygen_read8(chip, i + j));
+               snd_iprintf(buffer, "\n");
+       }
+       if (mutex_lock_interruptible(&chip->mutex) < 0)
+               return;
+       snd_iprintf(buffer, "\nAC97\n");
+       for (i = 0; i < 0x80; i += 0x10) {
+               snd_iprintf(buffer, "%02x:", i);
+               for (j = 0; j < 0x10; j += 2)
+                       snd_iprintf(buffer, " %04x",
+                                   oxygen_read_ac97(chip, 0, i + j));
+               snd_iprintf(buffer, "\n");
+       }
+       mutex_unlock(&chip->mutex);
+}
+
+static void __devinit oxygen_proc_init(struct oxygen *chip)
+{
+       struct snd_info_entry *entry;
+
+       if (!snd_card_proc_new(chip->card, "cmi8788", &entry))
+               snd_info_set_text_ops(entry, chip, oxygen_proc_read);
+}
+#else
+#define oxygen_proc_init(chip)
+#endif
+
+static void __devinit oxygen_init(struct oxygen *chip)
+{
+       unsigned int i;
+
+       chip->dac_routing = 1;
+       for (i = 0; i < 8; ++i)
+               chip->dac_volume[i] = 0xff;
+       chip->spdif_playback_enable = 1;
+       chip->spdif_bits = OXYGEN_SPDIF_C | OXYGEN_SPDIF_ORIGINAL |
+               (IEC958_AES1_CON_PCM_CODER << OXYGEN_SPDIF_CATEGORY_SHIFT);
+       chip->spdif_pcm_bits = chip->spdif_bits;
+
+       if (oxygen_read8(chip, OXYGEN_REVISION) & OXYGEN_REVISION_2)
+               chip->revision = 2;
+       else
+               chip->revision = 1;
+
+       if (chip->revision == 1)
+               oxygen_set_bits8(chip, OXYGEN_MISC, OXYGEN_MISC_MAGIC);
+
+       oxygen_set_bits8(chip, OXYGEN_FUNCTION,
+                        OXYGEN_FUNCTION_RESET_CODEC |
+                        OXYGEN_FUNCTION_ENABLE_SPI_4_5);
+       oxygen_write16(chip, OXYGEN_I2S_MULTICH_FORMAT, 0x010a);
+       oxygen_write16(chip, OXYGEN_I2S_A_FORMAT, 0x010a);
+       oxygen_write16(chip, OXYGEN_I2S_B_FORMAT, 0x010a);
+       oxygen_write16(chip, OXYGEN_I2S_C_FORMAT, 0x010a);
+       oxygen_set_bits32(chip, OXYGEN_SPDIF_CONTROL, OXYGEN_SPDIF_MAGIC2);
+       oxygen_write32(chip, OXYGEN_SPDIF_OUTPUT_BITS, chip->spdif_bits);
+       oxygen_write16(chip, OXYGEN_PLAY_ROUTING, 0x6c00);
+       oxygen_write8(chip, OXYGEN_REC_ROUTING, 0x10);
+       oxygen_write8(chip, OXYGEN_ADC_MONITOR, 0x00);
+       oxygen_write8(chip, OXYGEN_A_MONITOR_ROUTING, 0xe4);
+
+       oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0);
+       oxygen_write16(chip, OXYGEN_DMA_STATUS, 0);
+
+       oxygen_write8(chip, OXYGEN_AC97_INTERRUPT_MASK, 0x00);
+       oxygen_clear_bits16(chip, OXYGEN_AC97_OUT_CONFIG,
+                           OXYGEN_AC97_OUT_MAGIC3);
+       oxygen_set_bits16(chip, OXYGEN_AC97_IN_CONFIG,
+                         OXYGEN_AC97_IN_MAGIC3);
+       oxygen_write_ac97(chip, 0, AC97_RESET, 0);
+       msleep(1);
+       oxygen_ac97_set_bits(chip, 0, 0x70, 0x0300);
+       oxygen_ac97_set_bits(chip, 0, 0x64, 0x8043);
+       oxygen_ac97_set_bits(chip, 0, 0x62, 0x180f);
+       oxygen_write_ac97(chip, 0, AC97_MASTER, 0x0000);
+       oxygen_write_ac97(chip, 0, AC97_PC_BEEP, 0x8000);
+       oxygen_write_ac97(chip, 0, AC97_MIC, 0x8808);
+       oxygen_write_ac97(chip, 0, AC97_LINE, 0x0808);
+       oxygen_write_ac97(chip, 0, AC97_CD, 0x8808);
+       oxygen_write_ac97(chip, 0, AC97_VIDEO, 0x8808);
+       oxygen_write_ac97(chip, 0, AC97_AUX, 0x8808);
+       oxygen_write_ac97(chip, 0, AC97_REC_GAIN, 0x8000);
+       oxygen_write_ac97(chip, 0, AC97_CENTER_LFE_MASTER, 0x8080);
+       oxygen_write_ac97(chip, 0, AC97_SURROUND_MASTER, 0x8080);
+       oxygen_ac97_clear_bits(chip, 0, 0x72, 0x0001);
+       /* power down unused ADCs and DACs */
+       oxygen_ac97_set_bits(chip, 0, AC97_POWERDOWN,
+                            AC97_PD_PR0 | AC97_PD_PR1);
+       oxygen_ac97_set_bits(chip, 0, AC97_EXTENDED_STATUS,
+                            AC97_EA_PRI | AC97_EA_PRJ | AC97_EA_PRK);
+}
+
+static void oxygen_card_free(struct snd_card *card)
+{
+       struct oxygen *chip = card->private_data;
+
+       spin_lock_irq(&chip->reg_lock);
+       chip->interrupt_mask = 0;
+       chip->pcm_running = 0;
+       oxygen_write16(chip, OXYGEN_DMA_STATUS, 0);
+       oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0);
+       spin_unlock_irq(&chip->reg_lock);
+       if (chip->irq >= 0) {
+               free_irq(chip->irq, chip);
+               synchronize_irq(chip->irq);
+       }
+       flush_scheduled_work();
+       chip->model->cleanup(chip);
+       mutex_destroy(&chip->mutex);
+       pci_release_regions(chip->pci);
+       pci_disable_device(chip->pci);
+}
+
+int __devinit oxygen_pci_probe(struct pci_dev *pci, int index, char *id,
+                              const struct oxygen_model *model)
+{
+       struct snd_card *card;
+       struct oxygen *chip;
+       int err;
+
+       card = snd_card_new(index, id, model->owner, sizeof *chip);
+       if (!card)
+               return -ENOMEM;
+
+       chip = card->private_data;
+       chip->card = card;
+       chip->pci = pci;
+       chip->irq = -1;
+       chip->model = model;
+       spin_lock_init(&chip->reg_lock);
+       mutex_init(&chip->mutex);
+       INIT_WORK(&chip->spdif_input_bits_work,
+                 oxygen_spdif_input_bits_changed);
+
+       err = pci_enable_device(pci);
+       if (err < 0)
+               goto err_card;
+
+       err = pci_request_regions(pci, model->chip);
+       if (err < 0) {
+               snd_printk(KERN_ERR "cannot reserve PCI resources\n");
+               goto err_pci_enable;
+       }
+
+       if (!(pci_resource_flags(pci, 0) & IORESOURCE_IO) ||
+           pci_resource_len(pci, 0) < 0x100) {
+               snd_printk(KERN_ERR "invalid PCI I/O range\n");
+               err = -ENXIO;
+               goto err_pci_regions;
+       }
+       chip->addr = pci_resource_start(pci, 0);
+
+       pci_set_master(pci);
+       snd_card_set_dev(card, &pci->dev);
+       card->private_free = oxygen_card_free;
+
+       oxygen_init(chip);
+       model->init(chip);
+
+       err = request_irq(pci->irq, oxygen_interrupt, IRQF_SHARED,
+                         model->chip, chip);
+       if (err < 0) {
+               snd_printk(KERN_ERR "cannot grab interrupt %d\n", pci->irq);
+               goto err_card;
+       }
+       chip->irq = pci->irq;
+
+       strcpy(card->driver, model->chip);
+       strcpy(card->shortname, model->shortname);
+       sprintf(card->longname, "%s (rev %u) at %#lx, irq %i",
+               model->longname, chip->revision, chip->addr, chip->irq);
+       strcpy(card->mixername, model->chip);
+       snd_component_add(card, model->chip);
+
+       err = oxygen_pcm_init(chip);
+       if (err < 0)
+               goto err_card;
+
+       err = oxygen_mixer_init(chip);
+       if (err < 0)
+               goto err_card;
+
+       if (oxygen_read8(chip, OXYGEN_MISC) & OXYGEN_MISC_MIDI) {
+               err = snd_mpu401_uart_new(card, 0, MPU401_HW_CMIPCI,
+                                         chip->addr + OXYGEN_MPU401,
+                                         MPU401_INFO_INTEGRATED, 0, 0,
+                                         &chip->midi);
+               if (err < 0)
+                       goto err_card;
+       }
+
+       oxygen_proc_init(chip);
+
+       spin_lock_irq(&chip->reg_lock);
+       chip->interrupt_mask |= OXYGEN_INT_SPDIF_IN_CHANGE;
+       oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask);
+       spin_unlock_irq(&chip->reg_lock);
+
+       err = snd_card_register(card);
+       if (err < 0)
+               goto err_card;
+
+       pci_set_drvdata(pci, card);
+       return 0;
+
+err_pci_regions:
+       pci_release_regions(pci);
+err_pci_enable:
+       pci_disable_device(pci);
+err_card:
+       snd_card_free(card);
+       return err;
+}
+EXPORT_SYMBOL(oxygen_pci_probe);
+
+void __devexit oxygen_pci_remove(struct pci_dev *pci)
+{
+       snd_card_free(pci_get_drvdata(pci));
+       pci_set_drvdata(pci, NULL);
+}
+EXPORT_SYMBOL(oxygen_pci_remove);
diff --git a/sound/pci/oxygen/oxygen_mixer.c b/sound/pci/oxygen/oxygen_mixer.c
new file mode 100644 (file)
index 0000000..e252aba
--- /dev/null
@@ -0,0 +1,623 @@
+/*
+ * C-Media CMI8788 driver - mixer code
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this driver; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/driver.h>
+#include <linux/mutex.h>
+#include <sound/ac97_codec.h>
+#include <sound/asoundef.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include "oxygen.h"
+
+static int dac_volume_info(struct snd_kcontrol *ctl,
+                          struct snd_ctl_elem_info *info)
+{
+       struct oxygen *chip = ctl->private_data;
+
+       info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       info->count = 8;
+       info->value.integer.min = chip->model->dac_minimum_volume;
+       info->value.integer.max = 0xff;
+       return 0;
+}
+
+static int dac_volume_get(struct snd_kcontrol *ctl,
+                         struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+       unsigned int i;
+
+       mutex_lock(&chip->mutex);
+       for (i = 0; i < 8; ++i)
+               value->value.integer.value[i] = chip->dac_volume[i];
+       mutex_unlock(&chip->mutex);
+       return 0;
+}
+
+static int dac_volume_put(struct snd_kcontrol *ctl,
+                         struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+       unsigned int i;
+       int changed;
+
+       changed = 0;
+       mutex_lock(&chip->mutex);
+       for (i = 0; i < 8; ++i)
+               if (value->value.integer.value[i] != chip->dac_volume[i]) {
+                       chip->dac_volume[i] = value->value.integer.value[i];
+                       changed = 1;
+               }
+       if (changed)
+               chip->model->update_dac_volume(chip);
+       mutex_unlock(&chip->mutex);
+       return changed;
+}
+
+static int dac_mute_get(struct snd_kcontrol *ctl,
+                       struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+
+       mutex_lock(&chip->mutex);
+       value->value.integer.value[0] = !chip->dac_mute;
+       mutex_unlock(&chip->mutex);
+       return 0;
+}
+
+static int dac_mute_put(struct snd_kcontrol *ctl,
+                         struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+       int changed;
+
+       mutex_lock(&chip->mutex);
+       changed = !value->value.integer.value[0] != chip->dac_mute;
+       if (changed) {
+               chip->dac_mute = !value->value.integer.value[0];
+               chip->model->update_dac_mute(chip);
+       }
+       mutex_unlock(&chip->mutex);
+       return changed;
+}
+
+static int upmix_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)
+{
+       static const char *const names[3] = {
+               "Front", "Front+Rear", "Front+Rear+Side"
+       };
+       info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+       info->count = 1;
+       info->value.enumerated.items = 3;
+       if (info->value.enumerated.item > 2)
+               info->value.enumerated.item = 2;
+       strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+       return 0;
+}
+
+static int upmix_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+
+       mutex_lock(&chip->mutex);
+       value->value.enumerated.item[0] = chip->dac_routing;
+       mutex_unlock(&chip->mutex);
+       return 0;
+}
+
+void oxygen_update_dac_routing(struct oxygen *chip)
+{
+       /*
+        * hardware channel order: front, side, center/lfe, rear
+        * ALSA channel order:     front, rear, center/lfe, side
+        */
+       static const unsigned int reg_values[3] = {
+               0x6c00, 0x2c00, 0x2000
+       };
+       unsigned int reg_value;
+
+       if ((oxygen_read8(chip, OXYGEN_PLAY_CHANNELS) &
+            OXYGEN_PLAY_CHANNELS_MASK) == OXYGEN_PLAY_CHANNELS_2)
+               reg_value = reg_values[chip->dac_routing];
+       else
+               reg_value = 0x6c00;
+       oxygen_write16_masked(chip, OXYGEN_PLAY_ROUTING, reg_value, 0xff00);
+}
+
+static int upmix_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+       int changed;
+
+       mutex_lock(&chip->mutex);
+       changed = value->value.enumerated.item[0] != chip->dac_routing;
+       if (changed) {
+               chip->dac_routing = min(value->value.enumerated.item[0], 2u);
+               spin_lock_irq(&chip->reg_lock);
+               oxygen_update_dac_routing(chip);
+               spin_unlock_irq(&chip->reg_lock);
+       }
+       mutex_unlock(&chip->mutex);
+       return changed;
+}
+
+static int spdif_switch_get(struct snd_kcontrol *ctl,
+                           struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+
+       mutex_lock(&chip->mutex);
+       value->value.integer.value[0] = chip->spdif_playback_enable;
+       mutex_unlock(&chip->mutex);
+       return 0;
+}
+
+static unsigned int oxygen_spdif_rate(unsigned int oxygen_rate)
+{
+       switch (oxygen_rate) {
+       case OXYGEN_RATE_32000:
+               return IEC958_AES3_CON_FS_32000 << OXYGEN_SPDIF_CS_RATE_SHIFT;
+       case OXYGEN_RATE_44100:
+               return IEC958_AES3_CON_FS_44100 << OXYGEN_SPDIF_CS_RATE_SHIFT;
+       default: /* OXYGEN_RATE_48000 */
+               return IEC958_AES3_CON_FS_48000 << OXYGEN_SPDIF_CS_RATE_SHIFT;
+       case OXYGEN_RATE_64000:
+               return 0xb << OXYGEN_SPDIF_CS_RATE_SHIFT;
+       case OXYGEN_RATE_88200:
+               return 0x8 << OXYGEN_SPDIF_CS_RATE_SHIFT;
+       case OXYGEN_RATE_96000:
+               return 0xa << OXYGEN_SPDIF_CS_RATE_SHIFT;
+       case OXYGEN_RATE_176400:
+               return 0xc << OXYGEN_SPDIF_CS_RATE_SHIFT;
+       case OXYGEN_RATE_192000:
+               return 0xe << OXYGEN_SPDIF_CS_RATE_SHIFT;
+       }
+}
+
+void oxygen_update_spdif_source(struct oxygen *chip)
+{
+       u32 old_control, new_control;
+       u16 old_routing, new_routing;
+       unsigned int oxygen_rate;
+
+       old_control = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL);
+       old_routing = oxygen_read16(chip, OXYGEN_PLAY_ROUTING);
+       if (chip->pcm_active & (1 << PCM_SPDIF)) {
+               new_control = old_control | OXYGEN_SPDIF_OUT_ENABLE;
+               new_routing = (old_routing & ~0x00e0) | 0x0000;
+               oxygen_rate = (old_control >> OXYGEN_SPDIF_OUT_RATE_SHIFT)
+                       & OXYGEN_I2S_RATE_MASK;
+               /* S/PDIF rate was already set by the caller */
+       } else if ((chip->pcm_active & (1 << PCM_MULTICH)) &&
+                  chip->spdif_playback_enable) {
+               new_routing = (old_routing & ~0x00e0) | 0x0020;
+               oxygen_rate = oxygen_read16(chip, OXYGEN_I2S_MULTICH_FORMAT)
+                       & OXYGEN_I2S_RATE_MASK;
+               new_control = (old_control & ~OXYGEN_SPDIF_OUT_RATE_MASK) |
+                       (oxygen_rate << OXYGEN_SPDIF_OUT_RATE_SHIFT) |
+                       OXYGEN_SPDIF_OUT_ENABLE;
+       } else {
+               new_control = old_control & ~OXYGEN_SPDIF_OUT_ENABLE;
+               new_routing = old_routing;
+               oxygen_rate = OXYGEN_RATE_44100;
+       }
+       if (old_routing != new_routing) {
+               oxygen_write32(chip, OXYGEN_SPDIF_CONTROL,
+                              new_control & ~OXYGEN_SPDIF_OUT_ENABLE);
+               oxygen_write16(chip, OXYGEN_PLAY_ROUTING, new_routing);
+       }
+       if (new_control & OXYGEN_SPDIF_OUT_ENABLE)
+               oxygen_write32(chip, OXYGEN_SPDIF_OUTPUT_BITS,
+                              oxygen_spdif_rate(oxygen_rate) |
+                              ((chip->pcm_active & (1 << PCM_SPDIF)) ?
+                               chip->spdif_pcm_bits : chip->spdif_bits));
+       oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, new_control);
+}
+
+static int spdif_switch_put(struct snd_kcontrol *ctl,
+                           struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+       int changed;
+
+       mutex_lock(&chip->mutex);
+       changed = value->value.integer.value[0] != chip->spdif_playback_enable;
+       if (changed) {
+               chip->spdif_playback_enable = !!value->value.integer.value[0];
+               spin_lock_irq(&chip->reg_lock);
+               oxygen_update_spdif_source(chip);
+               spin_unlock_irq(&chip->reg_lock);
+       }
+       mutex_unlock(&chip->mutex);
+       return changed;
+}
+
+static int spdif_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)
+{
+       info->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+       info->count = 1;
+       return 0;
+}
+
+static void oxygen_to_iec958(u32 bits, struct snd_ctl_elem_value *value)
+{
+       value->value.iec958.status[0] =
+               bits & (OXYGEN_SPDIF_NONAUDIO | OXYGEN_SPDIF_C |
+                       OXYGEN_SPDIF_PREEMPHASIS);
+       value->value.iec958.status[1] = /* category and original */
+               bits >> OXYGEN_SPDIF_CATEGORY_SHIFT;
+}
+
+static u32 iec958_to_oxygen(struct snd_ctl_elem_value *value)
+{
+       u32 bits;
+
+       bits = value->value.iec958.status[0] &
+               (OXYGEN_SPDIF_NONAUDIO | OXYGEN_SPDIF_C |
+                OXYGEN_SPDIF_PREEMPHASIS);
+       bits |= value->value.iec958.status[1] << OXYGEN_SPDIF_CATEGORY_SHIFT;
+       if (bits & OXYGEN_SPDIF_NONAUDIO)
+               bits |= OXYGEN_SPDIF_V;
+       return bits;
+}
+
+static inline void write_spdif_bits(struct oxygen *chip, u32 bits)
+{
+       oxygen_write32_masked(chip, OXYGEN_SPDIF_OUTPUT_BITS, bits,
+                             OXYGEN_SPDIF_NONAUDIO |
+                             OXYGEN_SPDIF_C |
+                             OXYGEN_SPDIF_PREEMPHASIS |
+                             OXYGEN_SPDIF_CATEGORY_MASK |
+                             OXYGEN_SPDIF_ORIGINAL |
+                             OXYGEN_SPDIF_V);
+}
+
+static int spdif_default_get(struct snd_kcontrol *ctl,
+                            struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+
+       mutex_lock(&chip->mutex);
+       oxygen_to_iec958(chip->spdif_bits, value);
+       mutex_unlock(&chip->mutex);
+       return 0;
+}
+
+static int spdif_default_put(struct snd_kcontrol *ctl,
+                            struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+       u32 new_bits;
+       int changed;
+
+       new_bits = iec958_to_oxygen(value);
+       mutex_lock(&chip->mutex);
+       changed = new_bits != chip->spdif_bits;
+       if (changed) {
+               chip->spdif_bits = new_bits;
+               if (!(chip->pcm_active & (1 << PCM_SPDIF)))
+                       write_spdif_bits(chip, new_bits);
+       }
+       mutex_unlock(&chip->mutex);
+       return changed;
+}
+
+static int spdif_mask_get(struct snd_kcontrol *ctl,
+                         struct snd_ctl_elem_value *value)
+{
+       value->value.iec958.status[0] = IEC958_AES0_NONAUDIO |
+               IEC958_AES0_CON_NOT_COPYRIGHT | IEC958_AES0_CON_EMPHASIS;
+       value->value.iec958.status[1] =
+               IEC958_AES1_CON_CATEGORY | IEC958_AES1_CON_ORIGINAL;
+       return 0;
+}
+
+static int spdif_pcm_get(struct snd_kcontrol *ctl,
+                        struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+
+       mutex_lock(&chip->mutex);
+       oxygen_to_iec958(chip->spdif_pcm_bits, value);
+       mutex_unlock(&chip->mutex);
+       return 0;
+}
+
+static int spdif_pcm_put(struct snd_kcontrol *ctl,
+                        struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+       u32 new_bits;
+       int changed;
+
+       new_bits = iec958_to_oxygen(value);
+       mutex_lock(&chip->mutex);
+       changed = new_bits != chip->spdif_pcm_bits;
+       if (changed) {
+               chip->spdif_pcm_bits = new_bits;
+               if (chip->pcm_active & (1 << PCM_SPDIF))
+                       write_spdif_bits(chip, new_bits);
+       }
+       mutex_unlock(&chip->mutex);
+       return changed;
+}
+
+static int spdif_input_mask_get(struct snd_kcontrol *ctl,
+                               struct snd_ctl_elem_value *value)
+{
+       value->value.iec958.status[0] = 0xff;
+       value->value.iec958.status[1] = 0xff;
+       value->value.iec958.status[2] = 0xff;
+       value->value.iec958.status[3] = 0xff;
+       return 0;
+}
+
+static int spdif_input_default_get(struct snd_kcontrol *ctl,
+                                  struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+       u32 bits;
+
+       bits = oxygen_read32(chip, OXYGEN_SPDIF_INPUT_BITS);
+       value->value.iec958.status[0] = bits;
+       value->value.iec958.status[1] = bits >> 8;
+       value->value.iec958.status[2] = bits >> 16;
+       value->value.iec958.status[3] = bits >> 24;
+       return 0;
+}
+
+static int ac97_switch_get(struct snd_kcontrol *ctl,
+                          struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+       unsigned int index = ctl->private_value & 0xff;
+       unsigned int bitnr = (ctl->private_value >> 8) & 0xff;
+       int invert = ctl->private_value & (1 << 16);
+       u16 reg;
+
+       mutex_lock(&chip->mutex);
+       reg = oxygen_read_ac97(chip, 0, index);
+       mutex_unlock(&chip->mutex);
+       if (!(reg & (1 << bitnr)) ^ !invert)
+               value->value.integer.value[0] = 1;
+       else
+               value->value.integer.value[0] = 0;
+       return 0;
+}
+
+static int ac97_switch_put(struct snd_kcontrol *ctl,
+                          struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+       unsigned int index = ctl->private_value & 0xff;
+       unsigned int bitnr = (ctl->private_value >> 8) & 0xff;
+       int invert = ctl->private_value & (1 << 16);
+       u16 oldreg, newreg;
+       int change;
+
+       mutex_lock(&chip->mutex);
+       oldreg = oxygen_read_ac97(chip, 0, index);
+       newreg = oldreg;
+       if (!value->value.integer.value[0] ^ !invert)
+               newreg |= 1 << bitnr;
+       else
+               newreg &= ~(1 << bitnr);
+       change = newreg != oldreg;
+       if (change) {
+               oxygen_write_ac97(chip, 0, index, newreg);
+               if (index == AC97_LINE)
+                       oxygen_write_ac97_masked(chip, 0, 0x72,
+                                                !!(newreg & 0x8000), 0x0001);
+       }
+       mutex_unlock(&chip->mutex);
+       return change;
+}
+
+static int ac97_volume_info(struct snd_kcontrol *ctl,
+                           struct snd_ctl_elem_info *info)
+{
+       info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       info->count = 2;
+       info->value.integer.min = 0;
+       info->value.integer.max = 0x1f;
+       return 0;
+}
+
+static int ac97_volume_get(struct snd_kcontrol *ctl,
+                          struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+       unsigned int index = ctl->private_value;
+       u16 reg;
+
+       mutex_lock(&chip->mutex);
+       reg = oxygen_read_ac97(chip, 0, index);
+       mutex_unlock(&chip->mutex);
+       value->value.integer.value[0] = 31 - (reg & 0x1f);
+       value->value.integer.value[1] = 31 - ((reg >> 8) & 0x1f);
+       return 0;
+}
+
+static int ac97_volume_put(struct snd_kcontrol *ctl,
+                          struct snd_ctl_elem_value *value)
+{
+       struct oxygen *chip = ctl->private_data;
+       unsigned int index = ctl->private_value;
+       u16 oldreg, newreg;
+       int change;
+
+       mutex_lock(&chip->mutex);
+       oldreg = oxygen_read_ac97(chip, 0, index);
+       newreg = oldreg;
+       newreg = (newreg & ~0x1f) |
+               (31 - (value->value.integer.value[0] & 0x1f));
+       newreg = (newreg & ~0x1f00) |
+               ((31 - (value->value.integer.value[0] & 0x1f)) << 8);
+       change = newreg != oldreg;
+       if (change)
+               oxygen_write_ac97(chip, 0, index, newreg);
+       mutex_unlock(&chip->mutex);
+       return change;
+}
+
+#define AC97_SWITCH(xname, index, bitnr, invert) { \
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+               .name = xname, \
+               .info = snd_ctl_boolean_mono_info, \
+               .get = ac97_switch_get, \
+               .put = ac97_switch_put, \
+               .private_value = ((invert) << 16) | ((bitnr) << 8) | (index), \
+       }
+#define AC97_VOLUME(xname, index) { \
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+               .name = xname, \
+               .info = ac97_volume_info, \
+               .get = ac97_volume_get, \
+               .put = ac97_volume_put, \
+               .tlv = { .p = ac97_db_scale, }, \
+               .private_value = (index), \
+       }
+
+static DECLARE_TLV_DB_SCALE(ac97_db_scale, -3450, 150, 0);
+
+static const struct snd_kcontrol_new controls[] = {
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "PCM Playback Volume",
+               .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+                         SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+               .info = dac_volume_info,
+               .get = dac_volume_get,
+               .put = dac_volume_put,
+               .tlv = {
+                       .p = NULL, /* set later */
+               },
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "PCM Playback Switch",
+               .info = snd_ctl_boolean_mono_info,
+               .get = dac_mute_get,
+               .put = dac_mute_put,
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "Stereo Upmixing",
+               .info = upmix_info,
+               .get = upmix_get,
+               .put = upmix_put,
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
+               .info = snd_ctl_boolean_mono_info,
+               .get = spdif_switch_get,
+               .put = spdif_switch_put,
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+               .device = 1,
+               .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+               .info = spdif_info,
+               .get = spdif_default_get,
+               .put = spdif_default_put,
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+               .device = 1,
+               .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
+               .access = SNDRV_CTL_ELEM_ACCESS_READ,
+               .info = spdif_info,
+               .get = spdif_mask_get,
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+               .device = 1,
+               .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM),
+               .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+                         SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+               .info = spdif_info,
+               .get = spdif_pcm_get,
+               .put = spdif_pcm_put,
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+               .device = 1,
+               .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK),
+               .access = SNDRV_CTL_ELEM_ACCESS_READ,
+               .info = spdif_info,
+               .get = spdif_input_mask_get,
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+               .device = 1,
+               .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+               .access = SNDRV_CTL_ELEM_ACCESS_READ,
+               .info = spdif_info,
+               .get = spdif_input_default_get,
+       },
+       AC97_VOLUME("Mic Capture Volume", AC97_MIC),
+       AC97_SWITCH("Mic Capture Switch", AC97_MIC, 15, 1),
+       AC97_SWITCH("Mic Boost (+20dB)", AC97_MIC, 6, 0),
+       AC97_SWITCH("Line Capture Switch", AC97_LINE, 15, 1),
+       AC97_VOLUME("CD Capture Volume", AC97_CD),
+       AC97_SWITCH("CD Capture Switch", AC97_CD, 15, 1),
+       AC97_VOLUME("Aux Capture Volume", AC97_AUX),
+       AC97_SWITCH("Aux Capture Switch", AC97_AUX, 15, 1),
+};
+
+static void oxygen_any_ctl_free(struct snd_kcontrol *ctl)
+{
+       struct oxygen *chip = ctl->private_data;
+
+       /* I'm too lazy to write a function for each control :-) */
+       chip->spdif_pcm_ctl = NULL;
+       chip->spdif_input_bits_ctl = NULL;
+}
+
+int oxygen_mixer_init(struct oxygen *chip)
+{
+       unsigned int i;
+       struct snd_kcontrol *ctl;
+       int err;
+
+       for (i = 0; i < ARRAY_SIZE(controls); ++i) {
+               ctl = snd_ctl_new1(&controls[i], chip);
+               if (!ctl)
+                       return -ENOMEM;
+               if (!strcmp(ctl->id.name, "PCM Playback Volume"))
+                       ctl->tlv.p = chip->model->dac_tlv;
+               else if (chip->model->cd_in_from_video_in &&
+                        !strncmp(ctl->id.name, "CD Capture ", 11))
+                       ctl->private_value ^= AC97_CD ^ AC97_VIDEO;
+               err = snd_ctl_add(chip->card, ctl);
+               if (err < 0)
+                       return err;
+               if (!strcmp(ctl->id.name,
+                           SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM))) {
+                       chip->spdif_pcm_ctl = ctl;
+                       ctl->private_free = oxygen_any_ctl_free;
+               } else if (!strcmp(ctl->id.name,
+                                SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT))) {
+                       chip->spdif_input_bits_ctl = ctl;
+                       ctl->private_free = oxygen_any_ctl_free;
+               }
+       }
+       return chip->model->mixer_init ? chip->model->mixer_init(chip) : 0;
+}
diff --git a/sound/pci/oxygen/oxygen_pcm.c b/sound/pci/oxygen/oxygen_pcm.c
new file mode 100644 (file)
index 0000000..941399b
--- /dev/null
@@ -0,0 +1,726 @@
+/*
+ * C-Media CMI8788 driver - PCM code
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this driver; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/driver.h>
+#include <linux/pci.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "oxygen.h"
+
+static struct snd_pcm_hardware oxygen_hardware[PCM_COUNT] = {
+       [PCM_A] = {
+               .info = SNDRV_PCM_INFO_MMAP |
+                       SNDRV_PCM_INFO_MMAP_VALID |
+                       SNDRV_PCM_INFO_INTERLEAVED |
+                       SNDRV_PCM_INFO_PAUSE |
+                       SNDRV_PCM_INFO_SYNC_START,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE |
+                          SNDRV_PCM_FMTBIT_S32_LE,
+               .rates = SNDRV_PCM_RATE_44100 |
+                        SNDRV_PCM_RATE_48000 |
+                        SNDRV_PCM_RATE_96000 |
+                        SNDRV_PCM_RATE_192000,
+               .rate_min = 44100,
+               .rate_max = 192000,
+               .channels_min = 2,
+               .channels_max = 2,
+               .buffer_bytes_max = 256 * 1024,
+               .period_bytes_min = 128,
+               .period_bytes_max = 128 * 1024,
+               .periods_min = 2,
+               .periods_max = 2048,
+       },
+       [PCM_B] = {
+               .info = SNDRV_PCM_INFO_MMAP |
+                       SNDRV_PCM_INFO_MMAP_VALID |
+                       SNDRV_PCM_INFO_INTERLEAVED |
+                       SNDRV_PCM_INFO_PAUSE |
+                       SNDRV_PCM_INFO_SYNC_START,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE |
+                          SNDRV_PCM_FMTBIT_S32_LE,
+               .rates = SNDRV_PCM_RATE_32000 |
+                        SNDRV_PCM_RATE_44100 |
+                        SNDRV_PCM_RATE_48000 |
+                        SNDRV_PCM_RATE_64000 |
+                        SNDRV_PCM_RATE_88200 |
+                        SNDRV_PCM_RATE_96000 |
+                        SNDRV_PCM_RATE_176400 |
+                        SNDRV_PCM_RATE_192000,
+               .rate_min = 32000,
+               .rate_max = 192000,
+               .channels_min = 2,
+               .channels_max = 2,
+               .buffer_bytes_max = 256 * 1024,
+               .period_bytes_min = 128,
+               .period_bytes_max = 128 * 1024,
+               .periods_min = 2,
+               .periods_max = 2048,
+       },
+       [PCM_C] = {
+               .info = SNDRV_PCM_INFO_MMAP |
+                       SNDRV_PCM_INFO_MMAP_VALID |
+                       SNDRV_PCM_INFO_INTERLEAVED |
+                       SNDRV_PCM_INFO_PAUSE |
+                       SNDRV_PCM_INFO_SYNC_START,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE |
+                          SNDRV_PCM_FMTBIT_S32_LE,
+               .rates = SNDRV_PCM_RATE_44100 |
+                        SNDRV_PCM_RATE_48000 |
+                        SNDRV_PCM_RATE_88200 |
+                        SNDRV_PCM_RATE_96000,
+               .rate_min = 44100,
+               .rate_max = 96000,
+               .channels_min = 2,
+               .channels_max = 2,
+               .buffer_bytes_max = 256 * 1024,
+               .period_bytes_min = 128,
+               .period_bytes_max = 128 * 1024,
+               .periods_min = 2,
+               .periods_max = 2048,
+       },
+       [PCM_SPDIF] = {
+               .info = SNDRV_PCM_INFO_MMAP |
+                       SNDRV_PCM_INFO_MMAP_VALID |
+                       SNDRV_PCM_INFO_INTERLEAVED |
+                       SNDRV_PCM_INFO_PAUSE |
+                       SNDRV_PCM_INFO_SYNC_START,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE |
+                          SNDRV_PCM_FMTBIT_S32_LE,
+               .rates = SNDRV_PCM_RATE_32000 |
+                        SNDRV_PCM_RATE_44100 |
+                        SNDRV_PCM_RATE_48000 |
+                        SNDRV_PCM_RATE_64000 |
+                        SNDRV_PCM_RATE_88200 |
+                        SNDRV_PCM_RATE_96000 |
+                        SNDRV_PCM_RATE_176400 |
+                        SNDRV_PCM_RATE_192000,
+               .rate_min = 32000,
+               .rate_max = 192000,
+               .channels_min = 2,
+               .channels_max = 2,
+               .buffer_bytes_max = 256 * 1024,
+               .period_bytes_min = 128,
+               .period_bytes_max = 128 * 1024,
+               .periods_min = 2,
+               .periods_max = 2048,
+       },
+       [PCM_MULTICH] = {
+               .info = SNDRV_PCM_INFO_MMAP |
+                       SNDRV_PCM_INFO_MMAP_VALID |
+                       SNDRV_PCM_INFO_INTERLEAVED |
+                       SNDRV_PCM_INFO_PAUSE |
+                       SNDRV_PCM_INFO_SYNC_START,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE |
+                          SNDRV_PCM_FMTBIT_S32_LE,
+               .rates = SNDRV_PCM_RATE_32000 |
+                        SNDRV_PCM_RATE_44100 |
+                        SNDRV_PCM_RATE_48000 |
+                        SNDRV_PCM_RATE_64000 |
+                        SNDRV_PCM_RATE_88200 |
+                        SNDRV_PCM_RATE_96000 |
+                        SNDRV_PCM_RATE_176400 |
+                        SNDRV_PCM_RATE_192000,
+               .rate_min = 32000,
+               .rate_max = 192000,
+               .channels_min = 2,
+               .channels_max = 8,
+               .buffer_bytes_max = 2048 * 1024,
+               .period_bytes_min = 128,
+               .period_bytes_max = 256 * 1024,
+               .periods_min = 2,
+               .periods_max = 16384,
+       },
+       [PCM_AC97] = {
+               .info = SNDRV_PCM_INFO_MMAP |
+                       SNDRV_PCM_INFO_MMAP_VALID |
+                       SNDRV_PCM_INFO_INTERLEAVED |
+                       SNDRV_PCM_INFO_PAUSE |
+                       SNDRV_PCM_INFO_SYNC_START,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE,
+               .rates = SNDRV_PCM_RATE_48000,
+               .rate_min = 48000,
+               .rate_max = 48000,
+               .channels_min = 2,
+               .channels_max = 2,
+               .buffer_bytes_max = 256 * 1024,
+               .period_bytes_min = 128,
+               .period_bytes_max = 128 * 1024,
+               .periods_min = 2,
+               .periods_max = 2048,
+       },
+};
+
+static int oxygen_open(struct snd_pcm_substream *substream,
+                      unsigned int channel)
+{
+       struct oxygen *chip = snd_pcm_substream_chip(substream);
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       int err;
+
+       runtime->private_data = (void *)channel;
+       runtime->hw = oxygen_hardware[channel];
+       err = snd_pcm_hw_constraint_step(runtime, 0,
+                                        SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
+       if (err < 0)
+               return err;
+       err = snd_pcm_hw_constraint_step(runtime, 0,
+                                        SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
+       if (err < 0)
+               return err;
+       if (runtime->hw.formats & SNDRV_PCM_FMTBIT_S32_LE) {
+               err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+               if (err < 0)
+                       return err;
+       }
+       if (runtime->hw.channels_max > 2) {
+               err = snd_pcm_hw_constraint_step(runtime, 0,
+                                                SNDRV_PCM_HW_PARAM_CHANNELS,
+                                                2);
+               if (err < 0)
+                       return err;
+       }
+       snd_pcm_set_sync(substream);
+       chip->streams[channel] = substream;
+
+       mutex_lock(&chip->mutex);
+       chip->pcm_active |= 1 << channel;
+       if (channel == PCM_SPDIF) {
+               chip->spdif_pcm_bits = chip->spdif_bits;
+               chip->spdif_pcm_ctl->vd[0].access &=
+                       ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+               snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE |
+                              SNDRV_CTL_EVENT_MASK_INFO,
+                              &chip->spdif_pcm_ctl->id);
+       }
+       mutex_unlock(&chip->mutex);
+
+       return 0;
+}
+
+static int oxygen_rec_a_open(struct snd_pcm_substream *substream)
+{
+       return oxygen_open(substream, PCM_A);
+}
+
+static int oxygen_rec_b_open(struct snd_pcm_substream *substream)
+{
+       return oxygen_open(substream, PCM_B);
+}
+
+static int oxygen_rec_c_open(struct snd_pcm_substream *substream)
+{
+       return oxygen_open(substream, PCM_C);
+}
+
+static int oxygen_spdif_open(struct snd_pcm_substream *substream)
+{
+       return oxygen_open(substream, PCM_SPDIF);
+}
+
+static int oxygen_multich_open(struct snd_pcm_substream *substream)
+{
+       return oxygen_open(substream, PCM_MULTICH);
+}
+
+static int oxygen_ac97_open(struct snd_pcm_substream *substream)
+{
+       return oxygen_open(substream, PCM_AC97);
+}
+
+static int oxygen_close(struct snd_pcm_substream *substream)
+{
+       struct oxygen *chip = snd_pcm_substream_chip(substream);
+       unsigned int channel = (unsigned int)substream->runtime->private_data;
+
+       mutex_lock(&chip->mutex);
+       chip->pcm_active &= ~(1 << channel);
+       if (channel == PCM_SPDIF) {
+               chip->spdif_pcm_ctl->vd[0].access |=
+                       SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+               snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE |
+                              SNDRV_CTL_EVENT_MASK_INFO,
+                              &chip->spdif_pcm_ctl->id);
+       }
+       if (channel == PCM_SPDIF || channel == PCM_MULTICH)
+               oxygen_update_spdif_source(chip);
+       mutex_unlock(&chip->mutex);
+
+       chip->streams[channel] = NULL;
+       return 0;
+}
+
+static unsigned int oxygen_format(struct snd_pcm_hw_params *hw_params)
+{
+       if (params_format(hw_params) == SNDRV_PCM_FORMAT_S32_LE)
+               return OXYGEN_FORMAT_24;
+       else
+               return OXYGEN_FORMAT_16;
+}
+
+static unsigned int oxygen_rate(struct snd_pcm_hw_params *hw_params)
+{
+       switch (params_rate(hw_params)) {
+       case 32000:
+               return OXYGEN_RATE_32000;
+       case 44100:
+               return OXYGEN_RATE_44100;
+       default: /* 48000 */
+               return OXYGEN_RATE_48000;
+       case 64000:
+               return OXYGEN_RATE_64000;
+       case 88200:
+               return OXYGEN_RATE_88200;
+       case 96000:
+               return OXYGEN_RATE_96000;
+       case 176400:
+               return OXYGEN_RATE_176400;
+       case 192000:
+               return OXYGEN_RATE_192000;
+       }
+}
+
+static unsigned int oxygen_i2s_magic2(struct snd_pcm_hw_params *hw_params)
+{
+       return params_rate(hw_params) <= 96000 ? 0x10 : 0x00;
+}
+
+static unsigned int oxygen_i2s_format(struct snd_pcm_hw_params *hw_params)
+{
+       if (params_format(hw_params) == SNDRV_PCM_FORMAT_S32_LE)
+               return OXYGEN_I2S_FORMAT_24;
+       else
+               return OXYGEN_I2S_FORMAT_16;
+}
+
+static unsigned int oxygen_play_channels(struct snd_pcm_hw_params *hw_params)
+{
+       switch (params_channels(hw_params)) {
+       default: /* 2 */
+               return OXYGEN_PLAY_CHANNELS_2;
+       case 4:
+               return OXYGEN_PLAY_CHANNELS_4;
+       case 6:
+               return OXYGEN_PLAY_CHANNELS_6;
+       case 8:
+               return OXYGEN_PLAY_CHANNELS_8;
+       }
+}
+
+static const unsigned int channel_base_registers[PCM_COUNT] = {
+       [PCM_A] = OXYGEN_DMA_A_ADDRESS,
+       [PCM_B] = OXYGEN_DMA_B_ADDRESS,
+       [PCM_C] = OXYGEN_DMA_C_ADDRESS,
+       [PCM_SPDIF] = OXYGEN_DMA_SPDIF_ADDRESS,
+       [PCM_MULTICH] = OXYGEN_DMA_MULTICH_ADDRESS,
+       [PCM_AC97] = OXYGEN_DMA_AC97_ADDRESS,
+};
+
+static int oxygen_hw_params(struct snd_pcm_substream *substream,
+                           struct snd_pcm_hw_params *hw_params)
+{
+       struct oxygen *chip = snd_pcm_substream_chip(substream);
+       unsigned int channel = (unsigned int)substream->runtime->private_data;
+       int err;
+
+       err = snd_pcm_lib_malloc_pages(substream,
+                                      params_buffer_bytes(hw_params));
+       if (err < 0)
+               return err;
+
+       oxygen_write32(chip, channel_base_registers[channel],
+                      (u32)substream->runtime->dma_addr);
+       if (channel == PCM_MULTICH) {
+               oxygen_write32(chip, OXYGEN_DMA_MULTICH_COUNT,
+                              params_buffer_bytes(hw_params) / 4 - 1);
+               oxygen_write32(chip, OXYGEN_DMA_MULTICH_TCOUNT,
+                              params_period_bytes(hw_params) / 4 - 1);
+       } else {
+               oxygen_write16(chip, channel_base_registers[channel] + 4,
+                              params_buffer_bytes(hw_params) / 4 - 1);
+               oxygen_write16(chip, channel_base_registers[channel] + 6,
+                              params_period_bytes(hw_params) / 4 - 1);
+       }
+       return 0;
+}
+
+static int oxygen_rec_a_hw_params(struct snd_pcm_substream *substream,
+                                 struct snd_pcm_hw_params *hw_params)
+{
+       struct oxygen *chip = snd_pcm_substream_chip(substream);
+       int err;
+
+       err = oxygen_hw_params(substream, hw_params);
+       if (err < 0)
+               return err;
+
+       spin_lock_irq(&chip->reg_lock);
+       oxygen_write8_masked(chip, OXYGEN_REC_FORMAT,
+                            oxygen_format(hw_params) << OXYGEN_REC_FORMAT_A_SHIFT,
+                            OXYGEN_REC_FORMAT_A_MASK);
+       oxygen_write8_masked(chip, OXYGEN_I2S_A_FORMAT,
+                            oxygen_rate(hw_params) |
+                            oxygen_i2s_magic2(hw_params) |
+                            oxygen_i2s_format(hw_params),
+                            OXYGEN_I2S_RATE_MASK |
+                            OXYGEN_I2S_MAGIC2_MASK |
+                            OXYGEN_I2S_FORMAT_MASK);
+       oxygen_clear_bits8(chip, OXYGEN_REC_ROUTING, 0x08);
+       spin_unlock_irq(&chip->reg_lock);
+
+       mutex_lock(&chip->mutex);
+       chip->model->set_adc_params(chip, hw_params);
+       mutex_unlock(&chip->mutex);
+       return 0;
+}
+
+static int oxygen_rec_b_hw_params(struct snd_pcm_substream *substream,
+                                 struct snd_pcm_hw_params *hw_params)
+{
+       struct oxygen *chip = snd_pcm_substream_chip(substream);
+       int err;
+
+       err = oxygen_hw_params(substream, hw_params);
+       if (err < 0)
+               return err;
+
+       spin_lock_irq(&chip->reg_lock);
+       oxygen_write8_masked(chip, OXYGEN_REC_FORMAT,
+                            oxygen_format(hw_params) << OXYGEN_REC_FORMAT_B_SHIFT,
+                            OXYGEN_REC_FORMAT_B_MASK);
+       oxygen_write8_masked(chip, OXYGEN_I2S_B_FORMAT,
+                            oxygen_rate(hw_params) |
+                            oxygen_i2s_magic2(hw_params) |
+                            oxygen_i2s_format(hw_params),
+                            OXYGEN_I2S_RATE_MASK |
+                            OXYGEN_I2S_MAGIC2_MASK |
+                            OXYGEN_I2S_FORMAT_MASK);
+       oxygen_clear_bits8(chip, OXYGEN_REC_ROUTING, 0x10);
+       spin_unlock_irq(&chip->reg_lock);
+
+       mutex_lock(&chip->mutex);
+       chip->model->set_adc_params(chip, hw_params);
+       mutex_unlock(&chip->mutex);
+       return 0;
+}
+
+static int oxygen_rec_c_hw_params(struct snd_pcm_substream *substream,
+                                 struct snd_pcm_hw_params *hw_params)
+{
+       struct oxygen *chip = snd_pcm_substream_chip(substream);
+       int err;
+
+       err = oxygen_hw_params(substream, hw_params);
+       if (err < 0)
+               return err;
+
+       spin_lock_irq(&chip->reg_lock);
+       oxygen_write8_masked(chip, OXYGEN_REC_FORMAT,
+                            oxygen_format(hw_params) << OXYGEN_REC_FORMAT_C_SHIFT,
+                            OXYGEN_REC_FORMAT_C_MASK);
+       oxygen_clear_bits8(chip, OXYGEN_REC_ROUTING, 0x20);
+       spin_unlock_irq(&chip->reg_lock);
+       return 0;
+}
+
+static int oxygen_spdif_hw_params(struct snd_pcm_substream *substream,
+                                 struct snd_pcm_hw_params *hw_params)
+{
+       struct oxygen *chip = snd_pcm_substream_chip(substream);
+       int err;
+
+       err = oxygen_hw_params(substream, hw_params);
+       if (err < 0)
+               return err;
+
+       spin_lock_irq(&chip->reg_lock);
+       oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL,
+                           OXYGEN_SPDIF_OUT_ENABLE);
+       oxygen_write8_masked(chip, OXYGEN_PLAY_FORMAT,
+                            oxygen_format(hw_params) << OXYGEN_SPDIF_FORMAT_SHIFT,
+                            OXYGEN_SPDIF_FORMAT_MASK);
+       oxygen_write32_masked(chip, OXYGEN_SPDIF_CONTROL,
+                             oxygen_rate(hw_params) << OXYGEN_SPDIF_OUT_RATE_SHIFT,
+                             OXYGEN_SPDIF_OUT_RATE_MASK);
+       oxygen_update_spdif_source(chip);
+       spin_unlock_irq(&chip->reg_lock);
+       return 0;
+}
+
+static int oxygen_multich_hw_params(struct snd_pcm_substream *substream,
+                                   struct snd_pcm_hw_params *hw_params)
+{
+       struct oxygen *chip = snd_pcm_substream_chip(substream);
+       int err;
+
+       err = oxygen_hw_params(substream, hw_params);
+       if (err < 0)
+               return err;
+
+       spin_lock_irq(&chip->reg_lock);
+       oxygen_write8_masked(chip, OXYGEN_PLAY_CHANNELS,
+                            oxygen_play_channels(hw_params),
+                            OXYGEN_PLAY_CHANNELS_MASK);
+       oxygen_write8_masked(chip, OXYGEN_PLAY_FORMAT,
+                            oxygen_format(hw_params) << OXYGEN_MULTICH_FORMAT_SHIFT,
+                            OXYGEN_MULTICH_FORMAT_MASK);
+       oxygen_write16_masked(chip, OXYGEN_I2S_MULTICH_FORMAT,
+                             oxygen_rate(hw_params) | oxygen_i2s_format(hw_params),
+                             OXYGEN_I2S_RATE_MASK | OXYGEN_I2S_FORMAT_MASK);
+       oxygen_clear_bits16(chip, OXYGEN_PLAY_ROUTING, 0x001f);
+       oxygen_update_dac_routing(chip);
+       oxygen_update_spdif_source(chip);
+       spin_unlock_irq(&chip->reg_lock);
+
+       mutex_lock(&chip->mutex);
+       chip->model->set_dac_params(chip, hw_params);
+       mutex_unlock(&chip->mutex);
+       return 0;
+}
+
+static int oxygen_ac97_hw_params(struct snd_pcm_substream *substream,
+                                struct snd_pcm_hw_params *hw_params)
+{
+       struct oxygen *chip = snd_pcm_substream_chip(substream);
+       int err;
+
+       err = oxygen_hw_params(substream, hw_params);
+       if (err < 0)
+               return err;
+
+       spin_lock_irq(&chip->reg_lock);
+       oxygen_write8_masked(chip, OXYGEN_PLAY_FORMAT,
+                            oxygen_format(hw_params) << OXYGEN_AC97_FORMAT_SHIFT,
+                            OXYGEN_AC97_FORMAT_MASK);
+       spin_unlock_irq(&chip->reg_lock);
+       return 0;
+}
+
+static int oxygen_hw_free(struct snd_pcm_substream *substream)
+{
+       struct oxygen *chip = snd_pcm_substream_chip(substream);
+       unsigned int channel = (unsigned int)substream->runtime->private_data;
+
+       spin_lock_irq(&chip->reg_lock);
+       chip->interrupt_mask &= ~(1 << channel);
+       oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask);
+       spin_unlock_irq(&chip->reg_lock);
+
+       return snd_pcm_lib_free_pages(substream);
+}
+
+static int oxygen_spdif_hw_free(struct snd_pcm_substream *substream)
+{
+       struct oxygen *chip = snd_pcm_substream_chip(substream);
+
+       spin_lock_irq(&chip->reg_lock);
+       oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL,
+                           OXYGEN_SPDIF_OUT_ENABLE);
+       spin_unlock_irq(&chip->reg_lock);
+       return oxygen_hw_free(substream);
+}
+
+static int oxygen_prepare(struct snd_pcm_substream *substream)
+{
+       struct oxygen *chip = snd_pcm_substream_chip(substream);
+       unsigned int channel = (unsigned int)substream->runtime->private_data;
+       unsigned int channel_mask = 1 << channel;
+
+       spin_lock_irq(&chip->reg_lock);
+       oxygen_set_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask);
+       oxygen_clear_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask);
+
+       chip->interrupt_mask |= channel_mask;
+       oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask);
+       spin_unlock_irq(&chip->reg_lock);
+       return 0;
+}
+
+static int oxygen_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+       struct oxygen *chip = snd_pcm_substream_chip(substream);
+       struct snd_pcm_substream *s;
+       unsigned int mask = 0;
+       int running;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               running = 0;
+               break;
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               running = 1;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_pcm_group_for_each_entry(s, substream) {
+               if (snd_pcm_substream_chip(s) == chip) {
+                       mask |= 1 << (unsigned int)s->runtime->private_data;
+                       snd_pcm_trigger_done(s, substream);
+               }
+       }
+
+       spin_lock(&chip->reg_lock);
+       if (running)
+               chip->pcm_running |= mask;
+       else
+               chip->pcm_running &= ~mask;
+       oxygen_write8(chip, OXYGEN_DMA_STATUS, chip->pcm_running);
+       spin_unlock(&chip->reg_lock);
+       return 0;
+}
+
+static snd_pcm_uframes_t oxygen_pointer(struct snd_pcm_substream *substream)
+{
+       struct oxygen *chip = snd_pcm_substream_chip(substream);
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       unsigned int channel = (unsigned int)runtime->private_data;
+       u32 curr_addr;
+
+       /* no spinlock, this read should be atomic */
+       curr_addr = oxygen_read32(chip, channel_base_registers[channel]);
+       return bytes_to_frames(runtime, curr_addr - (u32)runtime->dma_addr);
+}
+
+static struct snd_pcm_ops oxygen_rec_a_ops = {
+       .open      = oxygen_rec_a_open,
+       .close     = oxygen_close,
+       .ioctl     = snd_pcm_lib_ioctl,
+       .hw_params = oxygen_rec_a_hw_params,
+       .hw_free   = oxygen_hw_free,
+       .prepare   = oxygen_prepare,
+       .trigger   = oxygen_trigger,
+       .pointer   = oxygen_pointer,
+};
+
+static struct snd_pcm_ops oxygen_rec_b_ops = {
+       .open      = oxygen_rec_b_open,
+       .close     = oxygen_close,
+       .ioctl     = snd_pcm_lib_ioctl,
+       .hw_params = oxygen_rec_b_hw_params,
+       .hw_free   = oxygen_hw_free,
+       .prepare   = oxygen_prepare,
+       .trigger   = oxygen_trigger,
+       .pointer   = oxygen_pointer,
+};
+
+static struct snd_pcm_ops oxygen_rec_c_ops = {
+       .open      = oxygen_rec_c_open,
+       .close     = oxygen_close,
+       .ioctl     = snd_pcm_lib_ioctl,
+       .hw_params = oxygen_rec_c_hw_params,
+       .hw_free   = oxygen_hw_free,
+       .prepare   = oxygen_prepare,
+       .trigger   = oxygen_trigger,
+       .pointer   = oxygen_pointer,
+};
+
+static struct snd_pcm_ops oxygen_spdif_ops = {
+       .open      = oxygen_spdif_open,
+       .close     = oxygen_close,
+       .ioctl     = snd_pcm_lib_ioctl,
+       .hw_params = oxygen_spdif_hw_params,
+       .hw_free   = oxygen_spdif_hw_free,
+       .prepare   = oxygen_prepare,
+       .trigger   = oxygen_trigger,
+       .pointer   = oxygen_pointer,
+};
+
+static struct snd_pcm_ops oxygen_multich_ops = {
+       .open      = oxygen_multich_open,
+       .close     = oxygen_close,
+       .ioctl     = snd_pcm_lib_ioctl,
+       .hw_params = oxygen_multich_hw_params,
+       .hw_free   = oxygen_hw_free,
+       .prepare   = oxygen_prepare,
+       .trigger   = oxygen_trigger,
+       .pointer   = oxygen_pointer,
+};
+
+static struct snd_pcm_ops oxygen_ac97_ops = {
+       .open      = oxygen_ac97_open,
+       .close     = oxygen_close,
+       .ioctl     = snd_pcm_lib_ioctl,
+       .hw_params = oxygen_ac97_hw_params,
+       .hw_free   = oxygen_hw_free,
+       .prepare   = oxygen_prepare,
+       .trigger   = oxygen_trigger,
+       .pointer   = oxygen_pointer,
+};
+
+static void oxygen_pcm_free(struct snd_pcm *pcm)
+{
+       snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int __devinit oxygen_pcm_init(struct oxygen *chip)
+{
+       struct snd_pcm *pcm;
+       int err;
+
+       err = snd_pcm_new(chip->card, "Analog", 0, 1, 1, &pcm);
+       if (err < 0)
+               return err;
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &oxygen_multich_ops);
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+                       chip->model->record_from_dma_b ?
+                       &oxygen_rec_b_ops : &oxygen_rec_a_ops);
+       pcm->private_data = chip;
+       pcm->private_free = oxygen_pcm_free;
+       strcpy(pcm->name, "Analog");
+       snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
+                                     SNDRV_DMA_TYPE_DEV,
+                                     snd_dma_pci_data(chip->pci),
+                                     512 * 1024, 2048 * 1024);
+       snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+                                     SNDRV_DMA_TYPE_DEV,
+                                     snd_dma_pci_data(chip->pci),
+                                     128 * 1024, 256 * 1024);
+
+       err = snd_pcm_new(chip->card, "Digital", 1, 1, 1, &pcm);
+       if (err < 0)
+               return err;
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &oxygen_spdif_ops);
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &oxygen_rec_c_ops);
+       pcm->private_data = chip;
+       pcm->private_free = oxygen_pcm_free;
+       strcpy(pcm->name, "Digital");
+       snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+                                             snd_dma_pci_data(chip->pci),
+                                             128 * 1024, 256 * 1024);
+
+       if (chip->has_2nd_ac97_codec) {
+               err = snd_pcm_new(chip->card, "AC97", 2, 1, 0, &pcm);
+               if (err < 0)
+                       return err;
+               snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+                               &oxygen_ac97_ops);
+               pcm->private_data = chip;
+               pcm->private_free = oxygen_pcm_free;
+               strcpy(pcm->name, "Front Panel");
+               snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+                                                     snd_dma_pci_data(chip->pci),
+                                                     128 * 1024, 256 * 1024);
+       }
+       return 0;
+}
diff --git a/sound/pci/oxygen/oxygen_regs.h b/sound/pci/oxygen/oxygen_regs.h
new file mode 100644 (file)
index 0000000..7a4726d
--- /dev/null
@@ -0,0 +1,246 @@
+#ifndef OXYGEN_REGS_H_INCLUDED
+#define OXYGEN_REGS_H_INCLUDED
+
+/* recording channel A */
+#define OXYGEN_DMA_A_ADDRESS           0x00    /* 32-bit base address */
+#define OXYGEN_DMA_A_COUNT             0x04    /* buffer counter (dwords) */
+#define OXYGEN_DMA_A_TCOUNT            0x06    /* interrupt counter (dwords) */
+
+/* recording channel B */
+#define OXYGEN_DMA_B_ADDRESS           0x08
+#define OXYGEN_DMA_B_COUNT             0x0c
+#define OXYGEN_DMA_B_TCOUNT            0x0e
+
+/* recording channel C */
+#define OXYGEN_DMA_C_ADDRESS           0x10
+#define OXYGEN_DMA_C_COUNT             0x14
+#define OXYGEN_DMA_C_TCOUNT            0x16
+
+/* SPDIF playback channel */
+#define OXYGEN_DMA_SPDIF_ADDRESS       0x18
+#define OXYGEN_DMA_SPDIF_COUNT         0x1c
+#define OXYGEN_DMA_SPDIF_TCOUNT                0x1e
+
+/* multichannel playback channel */
+#define OXYGEN_DMA_MULTICH_ADDRESS     0x20
+#define OXYGEN_DMA_MULTICH_COUNT       0x24    /* 32 bits */
+#define OXYGEN_DMA_MULTICH_TCOUNT      0x28    /* 32 bits */
+
+/* AC'97 (front panel) playback channel */
+#define OXYGEN_DMA_AC97_ADDRESS                0x30
+#define OXYGEN_DMA_AC97_COUNT          0x34
+#define OXYGEN_DMA_AC97_TCOUNT         0x36
+
+/* all registers 0x00..0x36 return current position on read */
+
+#define OXYGEN_DMA_STATUS              0x40    /* 1 = running, 0 = stop */
+#define  OXYGEN_CHANNEL_A              0x01
+#define  OXYGEN_CHANNEL_B              0x02
+#define  OXYGEN_CHANNEL_C              0x04
+#define  OXYGEN_CHANNEL_SPDIF          0x08
+#define  OXYGEN_CHANNEL_MULTICH                0x10
+#define  OXYGEN_CHANNEL_AC97           0x20
+
+#define OXYGEN_DMA_RESET               0x42
+/* OXYGEN_CHANNEL_* */
+
+#define OXYGEN_PLAY_CHANNELS           0x43
+#define  OXYGEN_PLAY_CHANNELS_MASK     0x03
+#define  OXYGEN_PLAY_CHANNELS_2                0x00
+#define  OXYGEN_PLAY_CHANNELS_4                0x01
+#define  OXYGEN_PLAY_CHANNELS_6                0x02
+#define  OXYGEN_PLAY_CHANNELS_8                0x03
+
+#define OXYGEN_INTERRUPT_MASK          0x44
+/* OXYGEN_CHANNEL_* */
+#define  OXYGEN_INT_SPDIF_IN_CHANGE    0x0100
+#define  OXYGEN_INT_GPIO               0x0800
+
+#define OXYGEN_INTERRUPT_STATUS                0x46
+/* OXYGEN_CHANNEL_* amd OXYGEN_INT_* */
+#define  OXYGEN_INT_MIDI               0x1000
+
+#define OXYGEN_MISC                    0x48
+#define  OXYGEN_MISC_MAGIC             0x20
+#define  OXYGEN_MISC_MIDI              0x40
+
+#define OXYGEN_REC_FORMAT              0x4a
+#define  OXYGEN_REC_FORMAT_A_MASK      0x03
+#define  OXYGEN_REC_FORMAT_A_SHIFT     0
+#define  OXYGEN_REC_FORMAT_B_MASK      0x0c
+#define  OXYGEN_REC_FORMAT_B_SHIFT     2
+#define  OXYGEN_REC_FORMAT_C_MASK      0x30
+#define  OXYGEN_REC_FORMAT_C_SHIFT     4
+#define  OXYGEN_FORMAT_16              0x00
+#define  OXYGEN_FORMAT_24              0x01
+#define  OXYGEN_FORMAT_32              0x02
+
+#define OXYGEN_PLAY_FORMAT             0x4b
+#define  OXYGEN_SPDIF_FORMAT_MASK      0x03
+#define  OXYGEN_SPDIF_FORMAT_SHIFT     0
+#define  OXYGEN_MULTICH_FORMAT_MASK    0x0c
+#define  OXYGEN_MULTICH_FORMAT_SHIFT   2
+#define  OXYGEN_AC97_FORMAT_MASK       0x30
+#define  OXYGEN_AC97_FORMAT_SHIFT      4
+/* OXYGEN_FORMAT_* */
+
+#define OXYGEN_REC_CHANNELS            0x4c
+#define  OXYGEN_REC_A_CHANNELS_MASK    0x07
+#define  OXYGEN_REC_CHANNELS_2         0x00
+#define  OXYGEN_REC_CHANNELS_4         0x01
+#define  OXYGEN_REC_CHANNELS_6         0x03    /* or 0x02 */
+#define  OXYGEN_REC_CHANNELS_8         0x04
+
+#define OXYGEN_FUNCTION                        0x50
+#define  OXYGEN_FUNCTION_RESET_CODEC   0x02
+#define  OXYGEN_FUNCTION_ENABLE_SPI_4_5        0x80
+
+#define OXYGEN_I2S_MULTICH_FORMAT      0x60
+#define  OXYGEN_I2S_RATE_MASK          0x0007
+#define  OXYGEN_RATE_32000             0x0000
+#define  OXYGEN_RATE_44100             0x0001
+#define  OXYGEN_RATE_48000             0x0002
+#define  OXYGEN_RATE_64000             0x0003
+#define  OXYGEN_RATE_88200             0x0004
+#define  OXYGEN_RATE_96000             0x0005
+#define  OXYGEN_RATE_176400            0x0006
+#define  OXYGEN_RATE_192000            0x0007
+#define  OXYGEN_I2S_MAGIC1_MASK                0x0008
+#define  OXYGEN_I2S_MAGIC2_MASK                0x0030
+#define  OXYGEN_I2S_FORMAT_MASK                0x00c0
+#define  OXYGEN_I2S_FORMAT_16          0x0000
+#define  OXYGEN_I2S_FORMAT_20          0x0040
+#define  OXYGEN_I2S_FORMAT_24          0x0080
+#define  OXYGEN_I2S_FORMAT_32          0x00c0
+
+#define OXYGEN_I2S_A_FORMAT            0x62
+#define OXYGEN_I2S_B_FORMAT            0x64
+#define OXYGEN_I2S_C_FORMAT            0x66
+/* OXYGEN_I2S_RATE_* and OXYGEN_I2S_FORMAT_* */
+
+#define OXYGEN_SPDIF_CONTROL           0x70
+#define  OXYGEN_SPDIF_OUT_ENABLE       0x00000002
+#define  OXYGEN_SPDIF_LOOPBACK         0x00000004
+#define  OXYGEN_SPDIF_MAGIC2           0x00000020
+#define  OXYGEN_SPDIF_MAGIC3           0x00000040
+#define  OXYGEN_SPDIF_IN_VALID         0x00001000
+#define  OXYGEN_SPDIF_IN_CHANGE                0x00008000      /* r/wc */
+#define  OXYGEN_SPDIF_IN_INVERT                0x00010000      /* ? */
+#define  OXYGEN_SPDIF_OUT_RATE_MASK    0x07000000
+#define  OXYGEN_SPDIF_OUT_RATE_SHIFT   24
+/* OXYGEN_RATE_* << OXYGEN_SPDIF_OUT_RATE_SHIFT */
+
+#define OXYGEN_SPDIF_OUTPUT_BITS       0x74
+#define  OXYGEN_SPDIF_NONAUDIO         0x00000002
+#define  OXYGEN_SPDIF_C                        0x00000004
+#define  OXYGEN_SPDIF_PREEMPHASIS      0x00000008
+#define  OXYGEN_SPDIF_CATEGORY_MASK    0x000007f0
+#define  OXYGEN_SPDIF_CATEGORY_SHIFT   4
+#define  OXYGEN_SPDIF_ORIGINAL         0x00000800
+#define  OXYGEN_SPDIF_CS_RATE_MASK     0x0000f000
+#define  OXYGEN_SPDIF_CS_RATE_SHIFT    12
+#define  OXYGEN_SPDIF_V                        0x00010000      /* 0 = valid */
+
+#define OXYGEN_SPDIF_INPUT_BITS                0x78
+/* 32 bits, IEC958_AES_* */
+
+#define OXYGEN_2WIRE_CONTROL           0x90
+#define  OXYGEN_2WIRE_DIR_MASK         0x01
+#define  OXYGEN_2WIRE_DIR_WRITE                0x00    /* ? */
+#define  OXYGEN_2WIRE_DIR_READ         0x01    /* ? */
+#define  OXYGEN_2WIRE_ADDRESS_MASK     0xfe    /* slave device address */
+#define  OXYGEN_2WIRE_ADDRESS_SHIFT    1
+
+#define OXYGEN_2WIRE_MAP               0x91    /* address, 8 bits */
+#define OXYGEN_2WIRE_DATA              0x92    /* data, 16 bits */
+
+#define OXYGEN_2WIRE_BUS_STATUS                0x94
+#define  OXYGEN_2WIRE_BUSY             0x01
+
+#define OXYGEN_SPI_CONTROL             0x98
+#define  OXYGEN_SPI_BUSY               0x01    /* read */
+#define  OXYGEN_SPI_TRIGGER_WRITE      0x01    /* write */
+#define  OXYGEN_SPI_DATA_LENGTH_MASK   0x02
+#define  OXYGEN_SPI_DATA_LENGTH_2      0x00
+#define  OXYGEN_SPI_DATA_LENGTH_3      0x02
+#define  OXYGEN_SPI_CODEC_MASK         0x70    /* 0..5 */
+#define  OXYGEN_SPI_CODEC_SHIFT                4
+#define  OXYGEN_SPI_MAGIC              0x80
+
+#define OXYGEN_SPI_DATA1               0x99
+#define OXYGEN_SPI_DATA2               0x9a
+#define OXYGEN_SPI_DATA3               0x9b
+
+#define OXYGEN_MPU401                  0xa0
+
+#define OXYGEN_GPI_DATA                        0xa4
+
+#define OXYGEN_GPI_INTERRUPT_MASK      0xa5
+
+#define OXYGEN_GPIO_DATA               0xa6
+
+#define OXYGEN_GPIO_CONTROL            0xa8
+/* 0: input, 1: output */
+
+#define OXYGEN_GPIO_INTERRUPT_MASK     0xaa
+
+#define OXYGEN_DEVICE_SENSE            0xac    /* ? */
+
+#define OXYGEN_PLAY_ROUTING            0xc0
+#define  OXYGEN_PLAY_DAC0_SOURCE_MASK  0x0300
+#define  OXYGEN_PLAY_DAC1_SOURCE_MASK  0x0700
+#define  OXYGEN_PLAY_DAC2_SOURCE_MASK  0x3000
+#define  OXYGEN_PLAY_DAC3_SOURCE_MASK  0x7000
+
+#define OXYGEN_REC_ROUTING             0xc2
+
+#define OXYGEN_ADC_MONITOR             0xc3
+#define  OXYGEN_ADC_MONITOR_MULTICH    0x01
+#define  OXYGEN_ADC_MONITOR_AC97       0x04
+#define  OXYGEN_ADC_MONITOR_SPDIF      0x10
+
+#define OXYGEN_A_MONITOR_ROUTING       0xc4
+
+#define OXYGEN_AC97_CONTROL            0xd0
+#define  OXYGEN_AC97_RESET1            0x0001
+#define  OXYGEN_AC97_RESET1_BUSY       0x0002
+#define  OXYGEN_AC97_RESET2            0x0008
+#define  OXYGEN_AC97_CODEC_0           0x0010
+#define  OXYGEN_AC97_CODEC_1           0x0020
+
+#define OXYGEN_AC97_INTERRUPT_MASK     0xd2
+
+#define OXYGEN_AC97_INTERRUPT_STATUS   0xd3
+#define  OXYGEN_AC97_READ_COMPLETE     0x01
+#define  OXYGEN_AC97_WRITE_COMPLETE    0x02
+
+#define OXYGEN_AC97_OUT_CONFIG         0xd4
+#define  OXYGEN_AC97_OUT_MAGIC1                0x00000011
+#define  OXYGEN_AC97_OUT_MAGIC2                0x00000033
+#define  OXYGEN_AC97_OUT_MAGIC3                0x0000ff00
+
+#define OXYGEN_AC97_IN_CONFIG          0xd8
+#define  OXYGEN_AC97_IN_MAGIC1         0x00000011
+#define  OXYGEN_AC97_IN_MAGIC2         0x00000033
+#define  OXYGEN_AC97_IN_MAGIC3         0x00000300
+
+#define OXYGEN_AC97_REGS               0xdc
+#define  OXYGEN_AC97_REG_DATA_MASK     0x0000ffff
+#define  OXYGEN_AC97_REG_ADDR_MASK     0x007f0000
+#define  OXYGEN_AC97_REG_ADDR_SHIFT    16
+#define  OXYGEN_AC97_REG_DIR_MASK      0x00800000
+#define  OXYGEN_AC97_REG_DIR_WRITE     0x00000000
+#define  OXYGEN_AC97_REG_DIR_READ      0x00800000
+#define  OXYGEN_AC97_REG_CODEC_MASK    0x01000000
+#define  OXYGEN_AC97_REG_CODEC_SHIFT   24
+
+#define OXYGEN_DMA_FLUSH               0xe1
+/* OXYGEN_CHANNEL_* */
+
+#define OXYGEN_CODEC_VERSION           0xe4
+
+#define OXYGEN_REVISION                        0xe6
+#define  OXYGEN_REVISION_2             0x08    /* bit flag */
+#define  OXYGEN_REVISION_8787          0x14    /* all 8 bits */
+
+#endif