v4.19.13 snapshot.
diff --git a/sound/pci/oxygen/Makefile b/sound/pci/oxygen/Makefile
new file mode 100644
index 0000000..0dfc4f8
--- /dev/null
+++ b/sound/pci/oxygen/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+snd-oxygen-lib-objs := oxygen_io.o oxygen_lib.o oxygen_mixer.o oxygen_pcm.o
+snd-oxygen-objs := oxygen.o xonar_dg_mixer.o xonar_dg.o
+snd-se6x-objs := se6x.o
+snd-virtuoso-objs := virtuoso.o xonar_lib.o \
+	xonar_pcm179x.o xonar_cs43xx.o xonar_wm87x6.o xonar_hdmi.o
+
+obj-$(CONFIG_SND_OXYGEN_LIB) += snd-oxygen-lib.o
+obj-$(CONFIG_SND_OXYGEN) += snd-oxygen.o
+obj-$(CONFIG_SND_SE6X) += snd-se6x.o
+obj-$(CONFIG_SND_VIRTUOSO) += snd-virtuoso.o
diff --git a/sound/pci/oxygen/ak4396.h b/sound/pci/oxygen/ak4396.h
new file mode 100644
index 0000000..a512234
--- /dev/null
+++ b/sound/pci/oxygen/ak4396.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef AK4396_H_INCLUDED
+#define AK4396_H_INCLUDED
+
+#define AK4396_WRITE		0x2000
+
+#define AK4396_CONTROL_1	0
+#define AK4396_CONTROL_2	1
+#define AK4396_CONTROL_3	2
+#define AK4396_LCH_ATT		3
+#define AK4396_RCH_ATT		4
+
+/* control 1 */
+#define AK4396_RSTN		0x01
+#define AK4396_DIF_MASK		0x0e
+#define AK4396_DIF_16_LSB	0x00
+#define AK4396_DIF_20_LSB	0x02
+#define AK4396_DIF_24_MSB	0x04
+#define AK4396_DIF_24_I2S	0x06
+#define AK4396_DIF_24_LSB	0x08
+#define AK4396_ACKS		0x80
+/* control 2 */
+#define AK4396_SMUTE		0x01
+#define AK4396_DEM_MASK		0x06
+#define AK4396_DEM_441		0x00
+#define AK4396_DEM_OFF		0x02
+#define AK4396_DEM_48		0x04
+#define AK4396_DEM_32		0x06
+#define AK4396_DFS_MASK		0x18
+#define AK4396_DFS_NORMAL	0x00
+#define AK4396_DFS_DOUBLE	0x08
+#define AK4396_DFS_QUAD		0x10
+#define AK4396_SLOW		0x20
+#define AK4396_DZFM		0x40
+#define AK4396_DZFE		0x80
+/* control 3 */
+#define AK4396_DZFB		0x04
+#define AK4396_DCKB		0x10
+#define AK4396_DCKS		0x20
+#define AK4396_DSDM		0x40
+#define AK4396_D_P_MASK		0x80
+#define AK4396_PCM		0x00
+#define AK4396_DSD		0x80
+
+#endif
diff --git a/sound/pci/oxygen/cm9780.h b/sound/pci/oxygen/cm9780.h
new file mode 100644
index 0000000..7efb119
--- /dev/null
+++ b/sound/pci/oxygen/cm9780.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef CM9780_H_INCLUDED
+#define CM9780_H_INCLUDED
+
+#define CM9780_JACK		0x62
+#define CM9780_MIXER		0x64
+#define CM9780_GPIO_SETUP	0x70
+#define CM9780_GPIO_STATUS	0x72
+
+/* jack control */
+#define CM9780_RSOE		0x0001
+#define CM9780_CBOE		0x0002
+#define CM9780_SSOE		0x0004
+#define CM9780_FROE		0x0008
+#define CM9780_HP2FMICOE	0x0010
+#define CM9780_CB2MICOE		0x0020
+#define CM9780_FMIC2LI		0x0040
+#define CM9780_FMIC2MIC		0x0080
+#define CM9780_HP2LI		0x0100
+#define CM9780_HP2MIC		0x0200
+#define CM9780_MIC2LI		0x0400
+#define CM9780_MIC2MIC		0x0800
+#define CM9780_LI2LI		0x1000
+#define CM9780_LI2MIC		0x2000
+#define CM9780_LO2LI		0x4000
+#define CM9780_LO2MIC		0x8000
+
+/* mixer control */
+#define CM9780_BSTSEL		0x0001
+#define CM9780_STRO_MIC		0x0002
+#define CM9780_SPDI_FREX	0x0004
+#define CM9780_SPDI_SSEX	0x0008
+#define CM9780_SPDI_CBEX	0x0010
+#define CM9780_SPDI_RSEX	0x0020
+#define CM9780_MIX2FR		0x0040
+#define CM9780_MIX2SS		0x0080
+#define CM9780_MIX2CB		0x0100
+#define CM9780_MIX2RS		0x0200
+#define CM9780_MIX2FR_EX	0x0400
+#define CM9780_MIX2SS_EX	0x0800
+#define CM9780_MIX2CB_EX	0x1000
+#define CM9780_MIX2RS_EX	0x2000
+#define CM9780_P47_IO		0x4000
+#define CM9780_PCBSW		0x8000
+
+/* GPIO setup */
+#define CM9780_GPI0EN		0x0001
+#define CM9780_GPI1EN		0x0002
+#define CM9780_SENSE_P		0x0004
+#define CM9780_LOCK_P		0x0008
+#define CM9780_GPIO0P		0x0010
+#define CM9780_GPIO1P		0x0020
+#define CM9780_GPIO0IO		0x0100
+#define CM9780_GPIO1IO		0x0200
+
+/* GPIO status */
+#define CM9780_GPO0		0x0001
+#define CM9780_GPO1		0x0002
+#define CM9780_GPIO0S		0x0010
+#define CM9780_GPIO1S		0x0020
+#define CM9780_GPII0S		0x0100
+#define CM9780_GPII1S		0x0200
+
+#endif
diff --git a/sound/pci/oxygen/cs2000.h b/sound/pci/oxygen/cs2000.h
new file mode 100644
index 0000000..aca0479
--- /dev/null
+++ b/sound/pci/oxygen/cs2000.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef CS2000_H_INCLUDED
+#define CS2000_H_INCLUDED
+
+#define CS2000_DEV_ID		0x01
+#define CS2000_DEV_CTRL		0x02
+#define CS2000_DEV_CFG_1	0x03
+#define CS2000_DEV_CFG_2	0x04
+#define CS2000_GLOBAL_CFG	0x05
+#define CS2000_RATIO_0		0x06 /* 32 bits, big endian */
+#define CS2000_RATIO_1		0x0a
+#define CS2000_RATIO_2		0x0e
+#define CS2000_RATIO_3		0x12
+#define CS2000_FUN_CFG_1	0x16
+#define CS2000_FUN_CFG_2	0x17
+#define CS2000_FUN_CFG_3	0x1e
+
+/* DEV_ID */
+#define CS2000_DEVICE_MASK		0xf8
+#define CS2000_REVISION_MASK		0x07
+
+/* DEV_CTRL */
+#define CS2000_UNLOCK			0x80
+#define CS2000_AUX_OUT_DIS		0x02
+#define CS2000_CLK_OUT_DIS		0x01
+
+/* DEV_CFG_1 */
+#define CS2000_R_MOD_SEL_MASK		0xe0
+#define CS2000_R_MOD_SEL_1		0x00
+#define CS2000_R_MOD_SEL_2		0x20
+#define CS2000_R_MOD_SEL_4		0x40
+#define CS2000_R_MOD_SEL_8		0x60
+#define CS2000_R_MOD_SEL_1_2		0x80
+#define CS2000_R_MOD_SEL_1_4		0xa0
+#define CS2000_R_MOD_SEL_1_8		0xc0
+#define CS2000_R_MOD_SEL_1_16		0xe0
+#define CS2000_R_SEL_MASK		0x18
+#define CS2000_R_SEL_SHIFT		3
+#define CS2000_AUX_OUT_SRC_MASK		0x06
+#define CS2000_AUX_OUT_SRC_REF_CLK	0x00
+#define CS2000_AUX_OUT_SRC_CLK_IN	0x02
+#define CS2000_AUX_OUT_SRC_CLK_OUT	0x04
+#define CS2000_AUX_OUT_SRC_PLL_LOCK	0x06
+#define CS2000_EN_DEV_CFG_1		0x01
+
+/* DEV_CFG_2 */
+#define CS2000_LOCK_CLK_MASK		0x06
+#define CS2000_LOCK_CLK_SHIFT		1
+#define CS2000_FRAC_N_SRC_MASK		0x01
+#define CS2000_FRAC_N_SRC_STATIC	0x00
+#define CS2000_FRAC_N_SRC_DYNAMIC	0x01
+
+/* GLOBAL_CFG */
+#define CS2000_FREEZE			0x08
+#define CS2000_EN_DEV_CFG_2		0x01
+
+/* FUN_CFG_1 */
+#define CS2000_CLK_SKIP_EN		0x80
+#define CS2000_AUX_LOCK_CFG_MASK	0x40
+#define CS2000_AUX_LOCK_CFG_PP_HIGH	0x00
+#define CS2000_AUX_LOCK_CFG_OD_LOW	0x40
+#define CS2000_REF_CLK_DIV_MASK		0x18
+#define CS2000_REF_CLK_DIV_4		0x00
+#define CS2000_REF_CLK_DIV_2		0x08
+#define CS2000_REF_CLK_DIV_1		0x10
+
+/* FUN_CFG_2 */
+#define CS2000_CLK_OUT_UNL		0x10
+#define CS2000_L_F_RATIO_CFG_MASK	0x08
+#define CS2000_L_F_RATIO_CFG_20_12	0x00
+#define CS2000_L_F_RATIO_CFG_12_20	0x08
+
+/* FUN_CFG_3 */
+#define CS2000_CLK_IN_BW_MASK		0x70
+#define CS2000_CLK_IN_BW_1		0x00
+#define CS2000_CLK_IN_BW_2		0x10
+#define CS2000_CLK_IN_BW_4		0x20
+#define CS2000_CLK_IN_BW_8		0x30
+#define CS2000_CLK_IN_BW_16		0x40
+#define CS2000_CLK_IN_BW_32		0x50
+#define CS2000_CLK_IN_BW_64		0x60
+#define CS2000_CLK_IN_BW_128		0x70
+
+#endif
diff --git a/sound/pci/oxygen/cs4245.h b/sound/pci/oxygen/cs4245.h
new file mode 100644
index 0000000..bb9f2c5
--- /dev/null
+++ b/sound/pci/oxygen/cs4245.h
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#define CS4245_CHIP_ID		0x01
+#define CS4245_POWER_CTRL	0x02
+#define CS4245_DAC_CTRL_1	0x03
+#define CS4245_ADC_CTRL		0x04
+#define CS4245_MCLK_FREQ	0x05
+#define CS4245_SIGNAL_SEL	0x06
+#define CS4245_PGA_B_CTRL	0x07
+#define CS4245_PGA_A_CTRL	0x08
+#define CS4245_ANALOG_IN	0x09
+#define CS4245_DAC_A_CTRL	0x0a
+#define CS4245_DAC_B_CTRL	0x0b
+#define CS4245_DAC_CTRL_2	0x0c
+#define CS4245_INT_STATUS	0x0d
+#define CS4245_INT_MASK		0x0e
+#define CS4245_INT_MODE_MSB	0x0f
+#define CS4245_INT_MODE_LSB	0x10
+
+/* Chip ID */
+#define CS4245_CHIP_PART_MASK	0xf0
+#define CS4245_CHIP_REV_MASK	0x0f
+
+/* Power Control */
+#define CS4245_FREEZE		0x80
+#define CS4245_PDN_MIC		0x08
+#define CS4245_PDN_ADC		0x04
+#define CS4245_PDN_DAC		0x02
+#define CS4245_PDN		0x01
+
+/* DAC Control */
+#define CS4245_DAC_FM_MASK	0xc0
+#define CS4245_DAC_FM_SINGLE	0x00
+#define CS4245_DAC_FM_DOUBLE	0x40
+#define CS4245_DAC_FM_QUAD	0x80
+#define CS4245_DAC_DIF_MASK	0x30
+#define CS4245_DAC_DIF_LJUST	0x00
+#define CS4245_DAC_DIF_I2S	0x10
+#define CS4245_DAC_DIF_RJUST_16	0x20
+#define CS4245_DAC_DIF_RJUST_24	0x30
+#define CS4245_RESERVED_1	0x08
+#define CS4245_MUTE_DAC		0x04
+#define CS4245_DEEMPH		0x02
+#define CS4245_DAC_MASTER	0x01
+
+/* ADC Control */
+#define CS4245_ADC_FM_MASK	0xc0
+#define CS4245_ADC_FM_SINGLE	0x00
+#define CS4245_ADC_FM_DOUBLE	0x40
+#define CS4245_ADC_FM_QUAD	0x80
+#define CS4245_ADC_DIF_MASK	0x10
+#define CS4245_ADC_DIF_LJUST	0x00
+#define CS4245_ADC_DIF_I2S	0x10
+#define CS4245_MUTE_ADC		0x04
+#define CS4245_HPF_FREEZE	0x02
+#define CS4245_ADC_MASTER	0x01
+
+/* MCLK Frequency */
+#define CS4245_MCLK1_MASK	0x70
+#define CS4245_MCLK1_SHIFT	4
+#define CS4245_MCLK2_MASK	0x07
+#define CS4245_MCLK2_SHIFT	0
+#define CS4245_MCLK_1		0
+#define CS4245_MCLK_1_5		1
+#define CS4245_MCLK_2		2
+#define CS4245_MCLK_3		3
+#define CS4245_MCLK_4		4
+
+/* Signal Selection */
+#define CS4245_A_OUT_SEL_MASK	0x60
+#define CS4245_A_OUT_SEL_HIZ	0x00
+#define CS4245_A_OUT_SEL_DAC	0x20
+#define CS4245_A_OUT_SEL_PGA	0x40
+#define CS4245_LOOP		0x02
+#define CS4245_ASYNCH		0x01
+
+/* Channel B/A PGA Control */
+#define CS4245_PGA_GAIN_MASK	0x3f
+
+/* ADC Input Control */
+#define CS4245_PGA_SOFT		0x10
+#define CS4245_PGA_ZERO		0x08
+#define CS4245_SEL_MASK		0x07
+#define CS4245_SEL_MIC		0x00
+#define CS4245_SEL_INPUT_1	0x01
+#define CS4245_SEL_INPUT_2	0x02
+#define CS4245_SEL_INPUT_3	0x03
+#define CS4245_SEL_INPUT_4	0x04
+#define CS4245_SEL_INPUT_5	0x05
+#define CS4245_SEL_INPUT_6	0x06
+
+/* DAC Channel A/B Volume Control */
+#define CS4245_VOL_MASK		0xff
+
+/* DAC Control 2 */
+#define CS4245_DAC_SOFT		0x80
+#define CS4245_DAC_ZERO		0x40
+#define CS4245_INVERT_DAC	0x20
+#define CS4245_INT_ACTIVE_HIGH	0x01
+
+/* Interrupt Status/Mask/Mode */
+#define CS4245_ADC_CLK_ERR	0x08
+#define CS4245_DAC_CLK_ERR	0x04
+#define CS4245_ADC_OVFL		0x02
+#define CS4245_ADC_UNDRFL	0x01
+
+#define CS4245_SPI_ADDRESS_S	(0x9e << 16)
+#define CS4245_SPI_WRITE_S	(0 << 16)
+
+#define CS4245_SPI_ADDRESS	0x9e
+#define CS4245_SPI_WRITE	0
+#define CS4245_SPI_READ		1
diff --git a/sound/pci/oxygen/cs4362a.h b/sound/pci/oxygen/cs4362a.h
new file mode 100644
index 0000000..1aef15e
--- /dev/null
+++ b/sound/pci/oxygen/cs4362a.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* register 01h */
+#define CS4362A_PDN		0x01
+#define CS4362A_DAC1_DIS	0x02
+#define CS4362A_DAC2_DIS	0x04
+#define CS4362A_DAC3_DIS	0x08
+#define CS4362A_MCLKDIV		0x20
+#define CS4362A_FREEZE		0x40
+#define CS4362A_CPEN		0x80
+/* register 02h */
+#define CS4362A_DIF_MASK	0x70
+#define CS4362A_DIF_LJUST	0x00
+#define CS4362A_DIF_I2S		0x10
+#define CS4362A_DIF_RJUST_16	0x20
+#define CS4362A_DIF_RJUST_24	0x30
+#define CS4362A_DIF_RJUST_20	0x40
+#define CS4362A_DIF_RJUST_18	0x50
+/* register 03h */
+#define CS4362A_MUTEC_MASK	0x03
+#define CS4362A_MUTEC_6		0x00
+#define CS4362A_MUTEC_1		0x01
+#define CS4362A_MUTEC_3		0x03
+#define CS4362A_AMUTE		0x04
+#define CS4362A_MUTEC_POL	0x08
+#define CS4362A_RMP_UP		0x10
+#define CS4362A_SNGLVOL		0x20
+#define CS4362A_ZERO_CROSS	0x40
+#define CS4362A_SOFT_RAMP	0x80
+/* register 04h */
+#define CS4362A_RMP_DN		0x01
+#define CS4362A_DEM_MASK	0x06
+#define CS4362A_DEM_NONE	0x00
+#define CS4362A_DEM_44100	0x02
+#define CS4362A_DEM_48000	0x04
+#define CS4362A_DEM_32000	0x06
+#define CS4362A_FILT_SEL	0x10
+/* register 05h */
+#define CS4362A_INV_A1		0x01
+#define CS4362A_INV_B1		0x02
+#define CS4362A_INV_A2		0x04
+#define CS4362A_INV_B2		0x08
+#define CS4362A_INV_A3		0x10
+#define CS4362A_INV_B3		0x20
+/* register 06h */
+#define CS4362A_FM_MASK		0x03
+#define CS4362A_FM_SINGLE	0x00
+#define CS4362A_FM_DOUBLE	0x01
+#define CS4362A_FM_QUAD		0x02
+#define CS4362A_FM_DSD		0x03
+#define CS4362A_ATAPI_MASK	0x7c
+#define CS4362A_ATAPI_B_MUTE	0x00
+#define CS4362A_ATAPI_B_R	0x04
+#define CS4362A_ATAPI_B_L	0x08
+#define CS4362A_ATAPI_B_LR	0x0c
+#define CS4362A_ATAPI_A_MUTE	0x00
+#define CS4362A_ATAPI_A_R	0x10
+#define CS4362A_ATAPI_A_L	0x20
+#define CS4362A_ATAPI_A_LR	0x30
+#define CS4362A_ATAPI_MIX_LR_VOL 0x40
+#define CS4362A_A_EQ_B		0x80
+/* register 07h */
+#define CS4362A_VOL_MASK		0x7f
+#define CS4362A_MUTE			0x80
+/* register 08h: like 07h */
+/* registers 09h..0Bh: like 06h..08h */
+/* registers 0Ch..0Eh: like 06h..08h */
+/* register 12h */
+#define CS4362A_REV_MASK	0x07
+#define CS4362A_PART_MASK	0xf8
+#define CS4362A_PART_CS4362A	0x50
diff --git a/sound/pci/oxygen/cs4398.h b/sound/pci/oxygen/cs4398.h
new file mode 100644
index 0000000..76cb9d7
--- /dev/null
+++ b/sound/pci/oxygen/cs4398.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* register 1 */
+#define CS4398_REV_MASK		0x07
+#define CS4398_PART_MASK	0xf8
+#define CS4398_PART_CS4398	0x70
+/* register 2 */
+#define CS4398_FM_MASK		0x03
+#define CS4398_FM_SINGLE	0x00
+#define CS4398_FM_DOUBLE	0x01
+#define CS4398_FM_QUAD		0x02
+#define CS4398_FM_DSD		0x03
+#define CS4398_DEM_MASK		0x0c
+#define CS4398_DEM_NONE		0x00
+#define CS4398_DEM_44100	0x04
+#define CS4398_DEM_48000	0x08
+#define CS4398_DEM_32000	0x0c
+#define CS4398_DIF_MASK		0x70
+#define CS4398_DIF_LJUST	0x00
+#define CS4398_DIF_I2S		0x10
+#define CS4398_DIF_RJUST_16	0x20
+#define CS4398_DIF_RJUST_24	0x30
+#define CS4398_DIF_RJUST_20	0x40
+#define CS4398_DIF_RJUST_18	0x50
+#define CS4398_DSD_SRC		0x80
+/* register 3 */
+#define CS4398_ATAPI_MASK	0x1f
+#define CS4398_ATAPI_B_MUTE	0x00
+#define CS4398_ATAPI_B_R	0x01
+#define CS4398_ATAPI_B_L	0x02
+#define CS4398_ATAPI_B_LR	0x03
+#define CS4398_ATAPI_A_MUTE	0x00
+#define CS4398_ATAPI_A_R	0x04
+#define CS4398_ATAPI_A_L	0x08
+#define CS4398_ATAPI_A_LR	0x0c
+#define CS4398_ATAPI_MIX_LR_VOL	0x10
+#define CS4398_INVERT_B		0x20
+#define CS4398_INVERT_A		0x40
+#define CS4398_VOL_B_EQ_A	0x80
+/* register 4 */
+#define CS4398_MUTEP_MASK	0x03
+#define CS4398_MUTEP_AUTO	0x00
+#define CS4398_MUTEP_LOW	0x02
+#define CS4398_MUTEP_HIGH	0x03
+#define CS4398_MUTE_B		0x08
+#define CS4398_MUTE_A		0x10
+#define CS4398_MUTEC_A_EQ_B	0x20
+#define CS4398_DAMUTE		0x40
+#define CS4398_PAMUTE		0x80
+/* register 5 */
+#define CS4398_VOL_A_MASK	0xff
+/* register 6 */
+#define CS4398_VOL_B_MASK	0xff
+/* register 7 */
+#define CS4398_DIR_DSD		0x01
+#define CS4398_FILT_SEL		0x04
+#define CS4398_RMP_DN		0x10
+#define CS4398_RMP_UP		0x20
+#define CS4398_ZERO_CROSS	0x40
+#define CS4398_SOFT_RAMP	0x80
+/* register 8 */
+#define CS4398_MCLKDIV3		0x08
+#define CS4398_MCLKDIV2		0x10
+#define CS4398_FREEZE		0x20
+#define CS4398_CPEN		0x40
+#define CS4398_PDN		0x80
+/* register 9 */
+#define CS4398_DSD_PM_EN	0x01
+#define CS4398_DSD_PM_MODE	0x02
+#define CS4398_INVALID_DSD	0x04
+#define CS4398_STATIC_DSD	0x08
diff --git a/sound/pci/oxygen/oxygen.c b/sound/pci/oxygen/oxygen.c
new file mode 100644
index 0000000..e36ed8a
--- /dev/null
+++ b/sound/pci/oxygen/oxygen.c
@@ -0,0 +1,880 @@
+/*
+ * C-Media CMI8788 driver for C-Media's reference design and similar models
+ *
+ * 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
+ */
+
+/*
+ * CMI8788:
+ *
+ *   SPI 0 -> 1st AK4396 (front)
+ *   SPI 1 -> 2nd AK4396 (surround)
+ *   SPI 2 -> 3rd AK4396 (center/LFE)
+ *   SPI 3 -> WM8785
+ *   SPI 4 -> 4th AK4396 (back)
+ *
+ *   GPIO 0 -> DFS0 of AK5385
+ *   GPIO 1 -> DFS1 of AK5385
+ *
+ * X-Meridian models:
+ *   GPIO 4 -> enable extension S/PDIF input
+ *   GPIO 6 -> enable on-board S/PDIF input
+ *
+ * Claro models:
+ *   GPIO 6 -> S/PDIF from optical (0) or coaxial (1) input
+ *   GPIO 8 -> enable headphone amplifier
+ *
+ * CM9780:
+ *
+ *   LINE_OUT -> input of ADC
+ *
+ *   AUX_IN <- aux
+ *   CD_IN  <- CD
+ *   MIC_IN <- mic
+ *
+ *   GPO 0 -> route line-in (0) or AC97 output (1) to ADC input
+ */
+
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <sound/ac97_codec.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include "oxygen.h"
+#include "xonar_dg.h"
+#include "ak4396.h"
+#include "wm8785.h"
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("C-Media CMI8788 driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{C-Media,CMI8786}"
+			",{C-Media,CMI8787}"
+			",{C-Media,CMI8788}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool 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");
+
+enum {
+	MODEL_CMEDIA_REF,
+	MODEL_MERIDIAN,
+	MODEL_MERIDIAN_2G,
+	MODEL_CLARO,
+	MODEL_CLARO_HALO,
+	MODEL_FANTASIA,
+	MODEL_SERENADE,
+	MODEL_2CH_OUTPUT,
+	MODEL_HG2PCI,
+	MODEL_XONAR_DG,
+	MODEL_XONAR_DGX,
+};
+
+static const struct pci_device_id oxygen_ids[] = {
+	/* C-Media's reference design */
+	{ OXYGEN_PCI_SUBID(0x10b0, 0x0216), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x10b0, 0x0217), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x10b0, 0x0218), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x10b0, 0x0219), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x13f6, 0x0001), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x13f6, 0x0010), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x13f6, 0x8788), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x147a, 0xa017), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x1a58, 0x0910), .driver_data = MODEL_CMEDIA_REF },
+	/* Asus Xonar DG */
+	{ OXYGEN_PCI_SUBID(0x1043, 0x8467), .driver_data = MODEL_XONAR_DG },
+	/* Asus Xonar DGX */
+	{ OXYGEN_PCI_SUBID(0x1043, 0x8521), .driver_data = MODEL_XONAR_DGX },
+	/* PCI 2.0 HD Audio */
+	{ OXYGEN_PCI_SUBID(0x13f6, 0x8782), .driver_data = MODEL_2CH_OUTPUT },
+	/* Kuroutoshikou CMI8787-HG2PCI */
+	{ OXYGEN_PCI_SUBID(0x13f6, 0xffff), .driver_data = MODEL_HG2PCI },
+	/* TempoTec HiFier Fantasia */
+	{ OXYGEN_PCI_SUBID(0x14c3, 0x1710), .driver_data = MODEL_FANTASIA },
+	/* TempoTec HiFier Serenade */
+	{ OXYGEN_PCI_SUBID(0x14c3, 0x1711), .driver_data = MODEL_SERENADE },
+	/* AuzenTech X-Meridian */
+	{ OXYGEN_PCI_SUBID(0x415a, 0x5431), .driver_data = MODEL_MERIDIAN },
+	/* AuzenTech X-Meridian 2G */
+	{ OXYGEN_PCI_SUBID(0x5431, 0x017a), .driver_data = MODEL_MERIDIAN_2G },
+	/* HT-Omega Claro */
+	{ OXYGEN_PCI_SUBID(0x7284, 0x9761), .driver_data = MODEL_CLARO },
+	/* HT-Omega Claro halo */
+	{ OXYGEN_PCI_SUBID(0x7284, 0x9781), .driver_data = MODEL_CLARO_HALO },
+	{ }
+};
+MODULE_DEVICE_TABLE(pci, oxygen_ids);
+
+
+#define GPIO_AK5385_DFS_MASK	0x0003
+#define GPIO_AK5385_DFS_NORMAL	0x0000
+#define GPIO_AK5385_DFS_DOUBLE	0x0001
+#define GPIO_AK5385_DFS_QUAD	0x0002
+
+#define GPIO_MERIDIAN_DIG_MASK	0x0050
+#define GPIO_MERIDIAN_DIG_EXT	0x0010
+#define GPIO_MERIDIAN_DIG_BOARD	0x0040
+
+#define GPIO_CLARO_DIG_COAX	0x0040
+#define GPIO_CLARO_HP		0x0100
+
+struct generic_data {
+	unsigned int dacs;
+	u8 ak4396_regs[4][5];
+	u16 wm8785_regs[3];
+};
+
+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, 1, 2, 4
+	};
+	struct generic_data *data = chip->model_data;
+
+	oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+			 OXYGEN_SPI_DATA_LENGTH_2 |
+			 OXYGEN_SPI_CLOCK_160 |
+			 (codec_spi_map[codec] << OXYGEN_SPI_CODEC_SHIFT) |
+			 OXYGEN_SPI_CEN_LATCH_CLOCK_HI,
+			 AK4396_WRITE | (reg << 8) | value);
+	data->ak4396_regs[codec][reg] = value;
+}
+
+static void ak4396_write_cached(struct oxygen *chip, unsigned int codec,
+				u8 reg, u8 value)
+{
+	struct generic_data *data = chip->model_data;
+
+	if (value != data->ak4396_regs[codec][reg])
+		ak4396_write(chip, codec, reg, value);
+}
+
+static void wm8785_write(struct oxygen *chip, u8 reg, unsigned int value)
+{
+	struct generic_data *data = chip->model_data;
+
+	oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+			 OXYGEN_SPI_DATA_LENGTH_2 |
+			 OXYGEN_SPI_CLOCK_160 |
+			 (3 << OXYGEN_SPI_CODEC_SHIFT) |
+			 OXYGEN_SPI_CEN_LATCH_CLOCK_LO,
+			 (reg << 9) | value);
+	if (reg < ARRAY_SIZE(data->wm8785_regs))
+		data->wm8785_regs[reg] = value;
+}
+
+static void ak4396_registers_init(struct oxygen *chip)
+{
+	struct generic_data *data = chip->model_data;
+	unsigned int i;
+
+	for (i = 0; i < data->dacs; ++i) {
+		ak4396_write(chip, i, AK4396_CONTROL_1,
+			     AK4396_DIF_24_MSB | AK4396_RSTN);
+		ak4396_write(chip, i, AK4396_CONTROL_2,
+			     data->ak4396_regs[0][AK4396_CONTROL_2]);
+		ak4396_write(chip, i, AK4396_CONTROL_3,
+			     AK4396_PCM);
+		ak4396_write(chip, i, AK4396_LCH_ATT,
+			     chip->dac_volume[i * 2]);
+		ak4396_write(chip, i, AK4396_RCH_ATT,
+			     chip->dac_volume[i * 2 + 1]);
+	}
+}
+
+static void ak4396_init(struct oxygen *chip)
+{
+	struct generic_data *data = chip->model_data;
+
+	data->dacs = chip->model.dac_channels_pcm / 2;
+	data->ak4396_regs[0][AK4396_CONTROL_2] =
+		AK4396_SMUTE | AK4396_DEM_OFF | AK4396_DFS_NORMAL;
+	ak4396_registers_init(chip);
+	snd_component_add(chip->card, "AK4396");
+}
+
+static void ak5385_init(struct oxygen *chip)
+{
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_AK5385_DFS_MASK);
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_AK5385_DFS_MASK);
+	snd_component_add(chip->card, "AK5385");
+}
+
+static void wm8785_registers_init(struct oxygen *chip)
+{
+	struct generic_data *data = chip->model_data;
+
+	wm8785_write(chip, WM8785_R7, 0);
+	wm8785_write(chip, WM8785_R0, data->wm8785_regs[0]);
+	wm8785_write(chip, WM8785_R2, data->wm8785_regs[2]);
+}
+
+static void wm8785_init(struct oxygen *chip)
+{
+	struct generic_data *data = chip->model_data;
+
+	data->wm8785_regs[0] =
+		WM8785_MCR_SLAVE | WM8785_OSR_SINGLE | WM8785_FORMAT_LJUST;
+	data->wm8785_regs[2] = WM8785_HPFR | WM8785_HPFL;
+	wm8785_registers_init(chip);
+	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)
+{
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL,
+			  GPIO_MERIDIAN_DIG_MASK);
+	oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+			      GPIO_MERIDIAN_DIG_BOARD, GPIO_MERIDIAN_DIG_MASK);
+	ak4396_init(chip);
+	ak5385_init(chip);
+}
+
+static void claro_enable_hp(struct oxygen *chip)
+{
+	msleep(300);
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CLARO_HP);
+	oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_CLARO_HP);
+}
+
+static void claro_init(struct oxygen *chip)
+{
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CLARO_DIG_COAX);
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_CLARO_DIG_COAX);
+	ak4396_init(chip);
+	wm8785_init(chip);
+	claro_enable_hp(chip);
+}
+
+static void claro_halo_init(struct oxygen *chip)
+{
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CLARO_DIG_COAX);
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_CLARO_DIG_COAX);
+	ak4396_init(chip);
+	ak5385_init(chip);
+	claro_enable_hp(chip);
+}
+
+static void fantasia_init(struct oxygen *chip)
+{
+	ak4396_init(chip);
+	snd_component_add(chip->card, "CS5340");
+}
+
+static void stereo_output_init(struct oxygen *chip)
+{
+	ak4396_init(chip);
+}
+
+static void generic_cleanup(struct oxygen *chip)
+{
+}
+
+static void claro_disable_hp(struct oxygen *chip)
+{
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_CLARO_HP);
+}
+
+static void claro_cleanup(struct oxygen *chip)
+{
+	claro_disable_hp(chip);
+}
+
+static void claro_suspend(struct oxygen *chip)
+{
+	claro_disable_hp(chip);
+}
+
+static void generic_resume(struct oxygen *chip)
+{
+	ak4396_registers_init(chip);
+	wm8785_registers_init(chip);
+}
+
+static void meridian_resume(struct oxygen *chip)
+{
+	ak4396_registers_init(chip);
+}
+
+static void claro_resume(struct oxygen *chip)
+{
+	ak4396_registers_init(chip);
+	claro_enable_hp(chip);
+}
+
+static void stereo_resume(struct oxygen *chip)
+{
+	ak4396_registers_init(chip);
+}
+
+static void set_ak4396_params(struct oxygen *chip,
+			      struct snd_pcm_hw_params *params)
+{
+	struct generic_data *data = chip->model_data;
+	unsigned int i;
+	u8 value;
+
+	value = data->ak4396_regs[0][AK4396_CONTROL_2] & ~AK4396_DFS_MASK;
+	if (params_rate(params) <= 54000)
+		value |= AK4396_DFS_NORMAL;
+	else if (params_rate(params) <= 108000)
+		value |= AK4396_DFS_DOUBLE;
+	else
+		value |= AK4396_DFS_QUAD;
+
+	msleep(1); /* wait for the new MCLK to become stable */
+
+	if (value != data->ak4396_regs[0][AK4396_CONTROL_2]) {
+		for (i = 0; i < data->dacs; ++i) {
+			ak4396_write(chip, i, AK4396_CONTROL_1,
+				     AK4396_DIF_24_MSB);
+			ak4396_write(chip, i, AK4396_CONTROL_2, value);
+			ak4396_write(chip, i, AK4396_CONTROL_1,
+				     AK4396_DIF_24_MSB | AK4396_RSTN);
+		}
+	}
+}
+
+static void update_ak4396_volume(struct oxygen *chip)
+{
+	struct generic_data *data = chip->model_data;
+	unsigned int i;
+
+	for (i = 0; i < data->dacs; ++i) {
+		ak4396_write_cached(chip, i, AK4396_LCH_ATT,
+				    chip->dac_volume[i * 2]);
+		ak4396_write_cached(chip, i, AK4396_RCH_ATT,
+				    chip->dac_volume[i * 2 + 1]);
+	}
+}
+
+static void update_ak4396_mute(struct oxygen *chip)
+{
+	struct generic_data *data = chip->model_data;
+	unsigned int i;
+	u8 value;
+
+	value = data->ak4396_regs[0][AK4396_CONTROL_2] & ~AK4396_SMUTE;
+	if (chip->dac_mute)
+		value |= AK4396_SMUTE;
+	for (i = 0; i < data->dacs; ++i)
+		ak4396_write_cached(chip, i, AK4396_CONTROL_2, value);
+}
+
+static void set_wm8785_params(struct oxygen *chip,
+			      struct snd_pcm_hw_params *params)
+{
+	struct generic_data *data = chip->model_data;
+	unsigned int value;
+
+	value = WM8785_MCR_SLAVE | WM8785_FORMAT_LJUST;
+	if (params_rate(params) <= 48000)
+		value |= WM8785_OSR_SINGLE;
+	else if (params_rate(params) <= 96000)
+		value |= WM8785_OSR_DOUBLE;
+	else
+		value |= WM8785_OSR_QUAD;
+	if (value != data->wm8785_regs[0]) {
+		wm8785_write(chip, WM8785_R7, 0);
+		wm8785_write(chip, WM8785_R0, value);
+		wm8785_write(chip, WM8785_R2, data->wm8785_regs[2]);
+	}
+}
+
+static void set_ak5385_params(struct oxygen *chip,
+			      struct snd_pcm_hw_params *params)
+{
+	unsigned int value;
+
+	if (params_rate(params) <= 54000)
+		value = GPIO_AK5385_DFS_NORMAL;
+	else if (params_rate(params) <= 108000)
+		value = GPIO_AK5385_DFS_DOUBLE;
+	else
+		value = GPIO_AK5385_DFS_QUAD;
+	oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+			      value, GPIO_AK5385_DFS_MASK);
+}
+
+static void set_no_params(struct oxygen *chip, struct snd_pcm_hw_params *params)
+{
+}
+
+static int rolloff_info(struct snd_kcontrol *ctl,
+			struct snd_ctl_elem_info *info)
+{
+	static const char *const names[2] = {
+		"Sharp Roll-off", "Slow Roll-off"
+	};
+
+	return snd_ctl_enum_info(info, 1, 2, names);
+}
+
+static int rolloff_get(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct generic_data *data = chip->model_data;
+
+	value->value.enumerated.item[0] =
+		(data->ak4396_regs[0][AK4396_CONTROL_2] & AK4396_SLOW) != 0;
+	return 0;
+}
+
+static int rolloff_put(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct generic_data *data = chip->model_data;
+	unsigned int i;
+	int changed;
+	u8 reg;
+
+	mutex_lock(&chip->mutex);
+	reg = data->ak4396_regs[0][AK4396_CONTROL_2];
+	if (value->value.enumerated.item[0])
+		reg |= AK4396_SLOW;
+	else
+		reg &= ~AK4396_SLOW;
+	changed = reg != data->ak4396_regs[0][AK4396_CONTROL_2];
+	if (changed) {
+		for (i = 0; i < data->dacs; ++i)
+			ak4396_write(chip, i, AK4396_CONTROL_2, reg);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static const struct snd_kcontrol_new rolloff_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "DAC Filter Playback Enum",
+	.info = rolloff_info,
+	.get = rolloff_get,
+	.put = rolloff_put,
+};
+
+static int hpf_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)
+{
+	static const char *const names[2] = {
+		"None", "High-pass Filter"
+	};
+
+	return snd_ctl_enum_info(info, 1, 2, names);
+}
+
+static int hpf_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct generic_data *data = chip->model_data;
+
+	value->value.enumerated.item[0] =
+		(data->wm8785_regs[WM8785_R2] & WM8785_HPFR) != 0;
+	return 0;
+}
+
+static int hpf_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct generic_data *data = chip->model_data;
+	unsigned int reg;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	reg = data->wm8785_regs[WM8785_R2] & ~(WM8785_HPFR | WM8785_HPFL);
+	if (value->value.enumerated.item[0])
+		reg |= WM8785_HPFR | WM8785_HPFL;
+	changed = reg != data->wm8785_regs[WM8785_R2];
+	if (changed)
+		wm8785_write(chip, WM8785_R2, reg);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static const struct snd_kcontrol_new hpf_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "ADC Filter Capture Enum",
+	.info = hpf_info,
+	.get = hpf_get,
+	.put = hpf_put,
+};
+
+static int meridian_dig_source_info(struct snd_kcontrol *ctl,
+				    struct snd_ctl_elem_info *info)
+{
+	static const char *const names[2] = { "On-board", "Extension" };
+
+	return snd_ctl_enum_info(info, 1, 2, names);
+}
+
+static int claro_dig_source_info(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_info *info)
+{
+	static const char *const names[2] = { "Optical", "Coaxial" };
+
+	return snd_ctl_enum_info(info, 1, 2, names);
+}
+
+static int meridian_dig_source_get(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+
+	value->value.enumerated.item[0] =
+		!!(oxygen_read16(chip, OXYGEN_GPIO_DATA) &
+		   GPIO_MERIDIAN_DIG_EXT);
+	return 0;
+}
+
+static int claro_dig_source_get(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+
+	value->value.enumerated.item[0] =
+		!!(oxygen_read16(chip, OXYGEN_GPIO_DATA) &
+		   GPIO_CLARO_DIG_COAX);
+	return 0;
+}
+
+static int meridian_dig_source_put(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u16 old_reg, new_reg;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	old_reg = oxygen_read16(chip, OXYGEN_GPIO_DATA);
+	new_reg = old_reg & ~GPIO_MERIDIAN_DIG_MASK;
+	if (value->value.enumerated.item[0] == 0)
+		new_reg |= GPIO_MERIDIAN_DIG_BOARD;
+	else
+		new_reg |= GPIO_MERIDIAN_DIG_EXT;
+	changed = new_reg != old_reg;
+	if (changed)
+		oxygen_write16(chip, OXYGEN_GPIO_DATA, new_reg);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int claro_dig_source_put(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u16 old_reg, new_reg;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	old_reg = oxygen_read16(chip, OXYGEN_GPIO_DATA);
+	new_reg = old_reg & ~GPIO_CLARO_DIG_COAX;
+	if (value->value.enumerated.item[0])
+		new_reg |= GPIO_CLARO_DIG_COAX;
+	changed = new_reg != old_reg;
+	if (changed)
+		oxygen_write16(chip, OXYGEN_GPIO_DATA, new_reg);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static const struct snd_kcontrol_new meridian_dig_source_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "IEC958 Source Capture Enum",
+	.info = meridian_dig_source_info,
+	.get = meridian_dig_source_get,
+	.put = meridian_dig_source_put,
+};
+
+static const struct snd_kcontrol_new claro_dig_source_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "IEC958 Source Capture Enum",
+	.info = claro_dig_source_info,
+	.get = claro_dig_source_get,
+	.put = claro_dig_source_put,
+};
+
+static int generic_mixer_init(struct oxygen *chip)
+{
+	return snd_ctl_add(chip->card, snd_ctl_new1(&rolloff_control, chip));
+}
+
+static int generic_wm8785_mixer_init(struct oxygen *chip)
+{
+	int err;
+
+	err = generic_mixer_init(chip);
+	if (err < 0)
+		return err;
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&hpf_control, chip));
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int meridian_mixer_init(struct oxygen *chip)
+{
+	int err;
+
+	err = generic_mixer_init(chip);
+	if (err < 0)
+		return err;
+	err = snd_ctl_add(chip->card,
+			  snd_ctl_new1(&meridian_dig_source_control, chip));
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int claro_mixer_init(struct oxygen *chip)
+{
+	int err;
+
+	err = generic_wm8785_mixer_init(chip);
+	if (err < 0)
+		return err;
+	err = snd_ctl_add(chip->card,
+			  snd_ctl_new1(&claro_dig_source_control, chip));
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int claro_halo_mixer_init(struct oxygen *chip)
+{
+	int err;
+
+	err = generic_mixer_init(chip);
+	if (err < 0)
+		return err;
+	err = snd_ctl_add(chip->card,
+			  snd_ctl_new1(&claro_dig_source_control, chip));
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static void dump_ak4396_registers(struct oxygen *chip,
+				  struct snd_info_buffer *buffer)
+{
+	struct generic_data *data = chip->model_data;
+	unsigned int dac, i;
+
+	for (dac = 0; dac < data->dacs; ++dac) {
+		snd_iprintf(buffer, "\nAK4396 %u:", dac + 1);
+		for (i = 0; i < 5; ++i)
+			snd_iprintf(buffer, " %02x", data->ak4396_regs[dac][i]);
+	}
+	snd_iprintf(buffer, "\n");
+}
+
+static void dump_wm8785_registers(struct oxygen *chip,
+				  struct snd_info_buffer *buffer)
+{
+	struct generic_data *data = chip->model_data;
+	unsigned int i;
+
+	snd_iprintf(buffer, "\nWM8785:");
+	for (i = 0; i < 3; ++i)
+		snd_iprintf(buffer, " %03x", data->wm8785_regs[i]);
+	snd_iprintf(buffer, "\n");
+}
+
+static void dump_oxygen_registers(struct oxygen *chip,
+				  struct snd_info_buffer *buffer)
+{
+	dump_ak4396_registers(chip, buffer);
+	dump_wm8785_registers(chip, buffer);
+}
+
+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",
+	.init = generic_init,
+	.mixer_init = generic_wm8785_mixer_init,
+	.cleanup = generic_cleanup,
+	.resume = generic_resume,
+	.set_dac_params = set_ak4396_params,
+	.set_adc_params = set_wm8785_params,
+	.update_dac_volume = update_ak4396_volume,
+	.update_dac_mute = update_ak4396_mute,
+	.dump_registers = dump_oxygen_registers,
+	.dac_tlv = ak4396_db_scale,
+	.model_data_size = sizeof(struct generic_data),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 PLAYBACK_2_TO_AC97_1 |
+			 CAPTURE_0_FROM_I2S_1 |
+			 CAPTURE_1_FROM_SPDIF |
+			 CAPTURE_2_FROM_AC97_1 |
+			 AC97_CD_INPUT,
+	.dac_channels_pcm = 8,
+	.dac_channels_mixer = 8,
+	.dac_volume_min = 0,
+	.dac_volume_max = 255,
+	.function_flags = OXYGEN_FUNCTION_SPI |
+			  OXYGEN_FUNCTION_ENABLE_SPI_4_5,
+	.dac_mclks = OXYGEN_MCLKS(256, 128, 128),
+	.adc_mclks = OXYGEN_MCLKS(256, 256, 128),
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
+
+static int get_oxygen_model(struct oxygen *chip,
+			    const struct pci_device_id *id)
+{
+	static const char *const names[] = {
+		[MODEL_MERIDIAN]	= "AuzenTech X-Meridian",
+		[MODEL_MERIDIAN_2G]	= "AuzenTech X-Meridian 2G",
+		[MODEL_CLARO]		= "HT-Omega Claro",
+		[MODEL_CLARO_HALO]	= "HT-Omega Claro halo",
+		[MODEL_FANTASIA]	= "TempoTec HiFier Fantasia",
+		[MODEL_SERENADE]	= "TempoTec HiFier Serenade",
+		[MODEL_HG2PCI]		= "CMI8787-HG2PCI",
+		[MODEL_XONAR_DG]        = "Xonar DG",
+		[MODEL_XONAR_DGX]       = "Xonar DGX",
+	};
+
+	chip->model = model_generic;
+	switch (id->driver_data) {
+	case MODEL_MERIDIAN:
+	case MODEL_MERIDIAN_2G:
+		chip->model.init = meridian_init;
+		chip->model.mixer_init = meridian_mixer_init;
+		chip->model.resume = meridian_resume;
+		chip->model.set_adc_params = set_ak5385_params;
+		chip->model.dump_registers = dump_ak4396_registers;
+		chip->model.device_config = PLAYBACK_0_TO_I2S |
+					    PLAYBACK_1_TO_SPDIF |
+					    CAPTURE_0_FROM_I2S_2 |
+					    CAPTURE_1_FROM_SPDIF;
+		if (id->driver_data == MODEL_MERIDIAN)
+			chip->model.device_config |= AC97_CD_INPUT;
+		break;
+	case MODEL_CLARO:
+		chip->model.init = claro_init;
+		chip->model.mixer_init = claro_mixer_init;
+		chip->model.cleanup = claro_cleanup;
+		chip->model.suspend = claro_suspend;
+		chip->model.resume = claro_resume;
+		break;
+	case MODEL_CLARO_HALO:
+		chip->model.init = claro_halo_init;
+		chip->model.mixer_init = claro_halo_mixer_init;
+		chip->model.cleanup = claro_cleanup;
+		chip->model.suspend = claro_suspend;
+		chip->model.resume = claro_resume;
+		chip->model.set_adc_params = set_ak5385_params;
+		chip->model.dump_registers = dump_ak4396_registers;
+		chip->model.device_config = PLAYBACK_0_TO_I2S |
+					    PLAYBACK_1_TO_SPDIF |
+					    CAPTURE_0_FROM_I2S_2 |
+					    CAPTURE_1_FROM_SPDIF;
+		break;
+	case MODEL_FANTASIA:
+	case MODEL_SERENADE:
+	case MODEL_2CH_OUTPUT:
+	case MODEL_HG2PCI:
+		chip->model.shortname = "C-Media CMI8787";
+		chip->model.chip = "CMI8787";
+		if (id->driver_data == MODEL_FANTASIA)
+			chip->model.init = fantasia_init;
+		else
+			chip->model.init = stereo_output_init;
+		chip->model.resume = stereo_resume;
+		chip->model.mixer_init = generic_mixer_init;
+		chip->model.set_adc_params = set_no_params;
+		chip->model.dump_registers = dump_ak4396_registers;
+		chip->model.device_config = PLAYBACK_0_TO_I2S |
+					    PLAYBACK_1_TO_SPDIF;
+		if (id->driver_data == MODEL_FANTASIA) {
+			chip->model.device_config |= CAPTURE_0_FROM_I2S_1;
+			chip->model.adc_mclks = OXYGEN_MCLKS(256, 128, 128);
+		}
+		chip->model.dac_channels_pcm = 2;
+		chip->model.dac_channels_mixer = 2;
+		break;
+	case MODEL_XONAR_DG:
+	case MODEL_XONAR_DGX:
+		chip->model = model_xonar_dg;
+		break;
+	}
+	if (id->driver_data == MODEL_MERIDIAN ||
+	    id->driver_data == MODEL_MERIDIAN_2G ||
+	    id->driver_data == MODEL_CLARO_HALO) {
+		chip->model.misc_flags = OXYGEN_MISC_MIDI;
+		chip->model.device_config |= MIDI_OUTPUT | MIDI_INPUT;
+	}
+	if (id->driver_data < ARRAY_SIZE(names) && names[id->driver_data])
+		chip->model.shortname = names[id->driver_data];
+	return 0;
+}
+
+static int generic_oxygen_probe(struct pci_dev *pci,
+				const struct pci_device_id *pci_id)
+{
+	static int dev;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		++dev;
+		return -ENOENT;
+	}
+	err = oxygen_pci_probe(pci, index[dev], id[dev], THIS_MODULE,
+			       oxygen_ids, get_oxygen_model);
+	if (err >= 0)
+		++dev;
+	return err;
+}
+
+static struct pci_driver oxygen_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = oxygen_ids,
+	.probe = generic_oxygen_probe,
+	.remove = oxygen_pci_remove,
+#ifdef CONFIG_PM_SLEEP
+	.driver = {
+		.pm = &oxygen_pci_pm,
+	},
+#endif
+};
+
+module_pci_driver(oxygen_driver);
diff --git a/sound/pci/oxygen/oxygen.h b/sound/pci/oxygen/oxygen.h
new file mode 100644
index 0000000..06bf7e5
--- /dev/null
+++ b/sound/pci/oxygen/oxygen.h
@@ -0,0 +1,260 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef OXYGEN_H_INCLUDED
+#define OXYGEN_H_INCLUDED
+
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/wait.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_MCLKS(f_single, f_double, f_quad) ((MCLK_##f_single << 0) | \
+						  (MCLK_##f_double << 2) | \
+						  (MCLK_##f_quad   << 4))
+
+#define OXYGEN_IO_SIZE	0x100
+
+#define OXYGEN_EEPROM_ID	0x434d	/* "CM" */
+
+/* model-specific configuration of outputs/inputs */
+#define PLAYBACK_0_TO_I2S	0x0001
+     /* PLAYBACK_0_TO_AC97_0		not implemented */
+#define PLAYBACK_1_TO_SPDIF	0x0004
+#define PLAYBACK_2_TO_AC97_1	0x0008
+#define CAPTURE_0_FROM_I2S_1	0x0010
+#define CAPTURE_0_FROM_I2S_2	0x0020
+     /* CAPTURE_0_FROM_AC97_0		not implemented */
+#define CAPTURE_1_FROM_SPDIF	0x0080
+#define CAPTURE_2_FROM_I2S_2	0x0100
+#define CAPTURE_2_FROM_AC97_1	0x0200
+#define CAPTURE_3_FROM_I2S_3	0x0400
+#define MIDI_OUTPUT		0x0800
+#define MIDI_INPUT		0x1000
+#define AC97_CD_INPUT		0x2000
+#define AC97_FMIC_SWITCH	0x4000
+
+enum {
+	CONTROL_SPDIF_PCM,
+	CONTROL_SPDIF_INPUT_BITS,
+	CONTROL_MIC_CAPTURE_SWITCH,
+	CONTROL_LINE_CAPTURE_SWITCH,
+	CONTROL_CD_CAPTURE_SWITCH,
+	CONTROL_AUX_CAPTURE_SWITCH,
+	CONTROL_COUNT
+};
+
+#define OXYGEN_PCI_SUBID(sv, sd) \
+	.vendor = PCI_VENDOR_ID_CMEDIA, \
+	.device = 0x8788, \
+	.subvendor = sv, \
+	.subdevice = sd
+
+#define BROKEN_EEPROM_DRIVER_DATA ((unsigned long)-1)
+#define OXYGEN_PCI_SUBID_BROKEN_EEPROM \
+	OXYGEN_PCI_SUBID(PCI_VENDOR_ID_CMEDIA, 0x8788), \
+	.driver_data = BROKEN_EEPROM_DRIVER_DATA
+
+struct pci_dev;
+struct pci_device_id;
+struct snd_card;
+struct snd_pcm_substream;
+struct snd_pcm_hardware;
+struct snd_pcm_hw_params;
+struct snd_kcontrol_new;
+struct snd_rawmidi;
+struct snd_info_buffer;
+struct oxygen;
+
+struct oxygen_model {
+	const char *shortname;
+	const char *longname;
+	const char *chip;
+	void (*init)(struct oxygen *chip);
+	int (*control_filter)(struct snd_kcontrol_new *template);
+	int (*mixer_init)(struct oxygen *chip);
+	void (*cleanup)(struct oxygen *chip);
+	void (*suspend)(struct oxygen *chip);
+	void (*resume)(struct oxygen *chip);
+	void (*pcm_hardware_filter)(unsigned int channel,
+				    struct snd_pcm_hardware *hardware);
+	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);
+	void (*update_center_lfe_mix)(struct oxygen *chip, bool mixed);
+	unsigned int (*adjust_dac_routing)(struct oxygen *chip,
+					   unsigned int play_routing);
+	void (*gpio_changed)(struct oxygen *chip);
+	void (*uart_input)(struct oxygen *chip);
+	void (*ac97_switch)(struct oxygen *chip,
+			    unsigned int reg, unsigned int mute);
+	void (*dump_registers)(struct oxygen *chip,
+			       struct snd_info_buffer *buffer);
+	const unsigned int *dac_tlv;
+	size_t model_data_size;
+	unsigned int device_config;
+	u8 dac_channels_pcm;
+	u8 dac_channels_mixer;
+	u8 dac_volume_min;
+	u8 dac_volume_max;
+	u8 misc_flags;
+	u8 function_flags;
+	u8 dac_mclks;
+	u8 adc_mclks;
+	u16 dac_i2s_format;
+	u16 adc_i2s_format;
+};
+
+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;
+	void *model_data;
+	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 has_ac97_0;
+	u8 has_ac97_1;
+	u32 spdif_bits;
+	u32 spdif_pcm_bits;
+	struct snd_pcm_substream *streams[PCM_COUNT];
+	struct snd_kcontrol *controls[CONTROL_COUNT];
+	struct work_struct spdif_input_bits_work;
+	struct work_struct gpio_work;
+	wait_queue_head_t ac97_waitqueue;
+	union {
+		u8 _8[OXYGEN_IO_SIZE];
+		__le16 _16[OXYGEN_IO_SIZE / 2];
+		__le32 _32[OXYGEN_IO_SIZE / 4];
+	} saved_registers;
+	u16 saved_ac97_registers[2][0x40];
+	unsigned int uart_input_count;
+	u8 uart_input[32];
+	struct oxygen_model model;
+};
+
+/* oxygen_lib.c */
+
+int oxygen_pci_probe(struct pci_dev *pci, int index, char *id,
+		     struct module *owner,
+		     const struct pci_device_id *ids,
+		     int (*get_model)(struct oxygen *chip,
+				      const struct pci_device_id *id
+				     )
+		    );
+void oxygen_pci_remove(struct pci_dev *pci);
+#ifdef CONFIG_PM_SLEEP
+extern const struct dev_pm_ops oxygen_pci_pm;
+#endif
+void oxygen_pci_shutdown(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);
+
+int oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data);
+void oxygen_write_i2c(struct oxygen *chip, u8 device, u8 map, u8 data);
+
+void oxygen_reset_uart(struct oxygen *chip);
+void oxygen_write_uart(struct oxygen *chip, u8 data);
+
+u16 oxygen_read_eeprom(struct oxygen *chip, unsigned int index);
+void oxygen_write_eeprom(struct oxygen *chip, unsigned int index, u16 value);
+
+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
index 0000000..c7851da
--- /dev/null
+++ b/sound/pci/oxygen/oxygen_io.c
@@ -0,0 +1,292 @@
+/*
+ * 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 <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/export.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/mpu401.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);
+	chip->saved_registers._8[reg] = value;
+}
+EXPORT_SYMBOL(oxygen_write8);
+
+void oxygen_write16(struct oxygen *chip, unsigned int reg, u16 value)
+{
+	outw(value, chip->addr + reg);
+	chip->saved_registers._16[reg / 2] = cpu_to_le16(value);
+}
+EXPORT_SYMBOL(oxygen_write16);
+
+void oxygen_write32(struct oxygen *chip, unsigned int reg, u32 value)
+{
+	outl(value, chip->addr + reg);
+	chip->saved_registers._32[reg / 4] = cpu_to_le32(value);
+}
+EXPORT_SYMBOL(oxygen_write32);
+
+void oxygen_write8_masked(struct oxygen *chip, unsigned int reg,
+			  u8 value, u8 mask)
+{
+	u8 tmp = inb(chip->addr + reg);
+	tmp &= ~mask;
+	tmp |= value & mask;
+	outb(tmp, chip->addr + reg);
+	chip->saved_registers._8[reg] = tmp;
+}
+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);
+	tmp &= ~mask;
+	tmp |= value & mask;
+	outw(tmp, chip->addr + reg);
+	chip->saved_registers._16[reg / 2] = cpu_to_le16(tmp);
+}
+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);
+	tmp &= ~mask;
+	tmp |= value & mask;
+	outl(tmp, chip->addr + reg);
+	chip->saved_registers._32[reg / 4] = cpu_to_le32(tmp);
+}
+EXPORT_SYMBOL(oxygen_write32_masked);
+
+static int oxygen_ac97_wait(struct oxygen *chip, unsigned int mask)
+{
+	u8 status = 0;
+
+	/*
+	 * Reading the status register also clears the bits, so we have to save
+	 * the read bits in status.
+	 */
+	wait_event_timeout(chip->ac97_waitqueue,
+			   ({ status |= oxygen_read8(chip, OXYGEN_AC97_INTERRUPT_STATUS);
+			      status & mask; }),
+			   msecs_to_jiffies(1) + 1);
+	/*
+	 * Check even after a timeout because this function should not require
+	 * the AC'97 interrupt to be enabled.
+	 */
+	status |= oxygen_read8(chip, OXYGEN_AC97_INTERRUPT_STATUS);
+	return status & mask ? 0 : -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_INT_WRITE_DONE) >= 0 &&
+		    ++succeeded >= 2) {
+			chip->saved_ac97_registers[codec][index / 2] = data;
+			return;
+		}
+	}
+	dev_err(chip->card->dev, "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_INT_READ_DONE) >= 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;
+		}
+	}
+	dev_err(chip->card->dev, "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);
+
+static int oxygen_wait_spi(struct oxygen *chip)
+{
+	unsigned int count;
+
+	/*
+	 * Higher timeout to be sure: 200 us;
+	 * actual transaction should not need more than 40 us.
+	 */
+	for (count = 50; count > 0; count--) {
+		udelay(4);
+		if ((oxygen_read8(chip, OXYGEN_SPI_CONTROL) &
+						OXYGEN_SPI_BUSY) == 0)
+			return 0;
+	}
+	dev_err(chip->card->dev, "oxygen: SPI wait timeout\n");
+	return -EIO;
+}
+
+int oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data)
+{
+	/*
+	 * We need to wait AFTER initiating the SPI transaction,
+	 * otherwise read operations will not work.
+	 */
+	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);
+	return oxygen_wait_spi(chip);
+}
+EXPORT_SYMBOL(oxygen_write_spi);
+
+void oxygen_write_i2c(struct oxygen *chip, u8 device, u8 map, u8 data)
+{
+	/* should not need more than about 300 us */
+	msleep(1);
+
+	oxygen_write8(chip, OXYGEN_2WIRE_MAP, map);
+	oxygen_write8(chip, OXYGEN_2WIRE_DATA, data);
+	oxygen_write8(chip, OXYGEN_2WIRE_CONTROL,
+		      device | OXYGEN_2WIRE_DIR_WRITE);
+}
+EXPORT_SYMBOL(oxygen_write_i2c);
+
+static void _write_uart(struct oxygen *chip, unsigned int port, u8 data)
+{
+	if (oxygen_read8(chip, OXYGEN_MPU401 + 1) & MPU401_TX_FULL)
+		msleep(1);
+	oxygen_write8(chip, OXYGEN_MPU401 + port, data);
+}
+
+void oxygen_reset_uart(struct oxygen *chip)
+{
+	_write_uart(chip, 1, MPU401_RESET);
+	msleep(1); /* wait for ACK */
+	_write_uart(chip, 1, MPU401_ENTER_UART);
+}
+EXPORT_SYMBOL(oxygen_reset_uart);
+
+void oxygen_write_uart(struct oxygen *chip, u8 data)
+{
+	_write_uart(chip, 0, data);
+}
+EXPORT_SYMBOL(oxygen_write_uart);
+
+u16 oxygen_read_eeprom(struct oxygen *chip, unsigned int index)
+{
+	unsigned int timeout;
+
+	oxygen_write8(chip, OXYGEN_EEPROM_CONTROL,
+		      index | OXYGEN_EEPROM_DIR_READ);
+	for (timeout = 0; timeout < 100; ++timeout) {
+		udelay(1);
+		if (!(oxygen_read8(chip, OXYGEN_EEPROM_STATUS)
+		      & OXYGEN_EEPROM_BUSY))
+			break;
+	}
+	return oxygen_read16(chip, OXYGEN_EEPROM_DATA);
+}
+
+void oxygen_write_eeprom(struct oxygen *chip, unsigned int index, u16 value)
+{
+	unsigned int timeout;
+
+	oxygen_write16(chip, OXYGEN_EEPROM_DATA, value);
+	oxygen_write8(chip, OXYGEN_EEPROM_CONTROL,
+		      index | OXYGEN_EEPROM_DIR_WRITE);
+	for (timeout = 0; timeout < 10; ++timeout) {
+		msleep(1);
+		if (!(oxygen_read8(chip, OXYGEN_EEPROM_STATUS)
+		      & OXYGEN_EEPROM_BUSY))
+			return;
+	}
+	dev_err(chip->card->dev, "EEPROM write timeout\n");
+}
diff --git a/sound/pci/oxygen/oxygen_lib.c b/sound/pci/oxygen/oxygen_lib.c
new file mode 100644
index 0000000..b4ef580
--- /dev/null
+++ b/sound/pci/oxygen/oxygen_lib.c
@@ -0,0 +1,834 @@
+/*
+ * 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 <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.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"
+#include "cm9780.h"
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("C-Media CMI8788 helper library");
+MODULE_LICENSE("GPL v2");
+
+#define DRIVER "oxygen"
+
+static inline int oxygen_uart_input_ready(struct oxygen *chip)
+{
+	return !(oxygen_read8(chip, OXYGEN_MPU401 + 1) & MPU401_RX_EMPTY);
+}
+
+static void oxygen_read_uart(struct oxygen *chip)
+{
+	if (unlikely(!oxygen_uart_input_ready(chip))) {
+		/* no data, but read it anyway to clear the interrupt */
+		oxygen_read8(chip, OXYGEN_MPU401);
+		return;
+	}
+	do {
+		u8 data = oxygen_read8(chip, OXYGEN_MPU401);
+		if (data == MPU401_ACK)
+			continue;
+		if (chip->uart_input_count >= ARRAY_SIZE(chip->uart_input))
+			chip->uart_input_count = 0;
+		chip->uart_input[chip->uart_input_count++] = data;
+	} while (oxygen_uart_input_ready(chip));
+	if (chip->model.uart_input)
+		chip->model.uart_input(chip);
+}
+
+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_DETECT |
+			  OXYGEN_INT_GPIO |
+			  OXYGEN_INT_AC97);
+	if (clear) {
+		if (clear & OXYGEN_INT_SPDIF_IN_DETECT)
+			chip->interrupt_mask &= ~OXYGEN_INT_SPDIF_IN_DETECT;
+		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_DETECT) {
+		spin_lock(&chip->reg_lock);
+		i = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL);
+		if (i & (OXYGEN_SPDIF_SENSE_INT | OXYGEN_SPDIF_LOCK_INT |
+			 OXYGEN_SPDIF_RATE_INT)) {
+			/* write the interrupt bit(s) to clear */
+			oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, i);
+			schedule_work(&chip->spdif_input_bits_work);
+		}
+		spin_unlock(&chip->reg_lock);
+	}
+
+	if (status & OXYGEN_INT_GPIO)
+		schedule_work(&chip->gpio_work);
+
+	if (status & OXYGEN_INT_MIDI) {
+		if (chip->midi)
+			snd_mpu401_uart_interrupt(0, chip->midi->private_data);
+		else
+			oxygen_read_uart(chip);
+	}
+
+	if (status & OXYGEN_INT_AC97)
+		wake_up(&chip->ac97_waitqueue);
+
+	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);
+	u32 reg;
+
+	/*
+	 * This function gets called when there is new activity on the SPDIF
+	 * input, or when we lose lock on the input signal, or when the rate
+	 * changes.
+	 */
+	msleep(1);
+	spin_lock_irq(&chip->reg_lock);
+	reg = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL);
+	if ((reg & (OXYGEN_SPDIF_SENSE_STATUS |
+		    OXYGEN_SPDIF_LOCK_STATUS))
+	    == OXYGEN_SPDIF_SENSE_STATUS) {
+		/*
+		 * If we detect activity on the SPDIF input but cannot lock to
+		 * a signal, the clock bit is likely to be wrong.
+		 */
+		reg ^= OXYGEN_SPDIF_IN_CLOCK_MASK;
+		oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, reg);
+		spin_unlock_irq(&chip->reg_lock);
+		msleep(1);
+		spin_lock_irq(&chip->reg_lock);
+		reg = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL);
+		if ((reg & (OXYGEN_SPDIF_SENSE_STATUS |
+			    OXYGEN_SPDIF_LOCK_STATUS))
+		    == OXYGEN_SPDIF_SENSE_STATUS) {
+			/* nothing detected with either clock; give up */
+			if ((reg & OXYGEN_SPDIF_IN_CLOCK_MASK)
+			    == OXYGEN_SPDIF_IN_CLOCK_192) {
+				/*
+				 * Reset clock to <= 96 kHz because this is
+				 * more likely to be received next time.
+				 */
+				reg &= ~OXYGEN_SPDIF_IN_CLOCK_MASK;
+				reg |= OXYGEN_SPDIF_IN_CLOCK_96;
+				oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, reg);
+			}
+		}
+	}
+	spin_unlock_irq(&chip->reg_lock);
+
+	if (chip->controls[CONTROL_SPDIF_INPUT_BITS]) {
+		spin_lock_irq(&chip->reg_lock);
+		chip->interrupt_mask |= OXYGEN_INT_SPDIF_IN_DETECT;
+		oxygen_write16(chip, OXYGEN_INTERRUPT_MASK,
+			       chip->interrupt_mask);
+		spin_unlock_irq(&chip->reg_lock);
+
+		/*
+		 * We don't actually know that any channel status bits have
+		 * changed, but let's send a notification just to be sure.
+		 */
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &chip->controls[CONTROL_SPDIF_INPUT_BITS]->id);
+	}
+}
+
+static void oxygen_gpio_changed(struct work_struct *work)
+{
+	struct oxygen *chip = container_of(work, struct oxygen, gpio_work);
+
+	if (chip->model.gpio_changed)
+		chip->model.gpio_changed(chip);
+}
+
+static void oxygen_proc_read(struct snd_info_entry *entry,
+			     struct snd_info_buffer *buffer)
+{
+	struct oxygen *chip = entry->private_data;
+	int i, j;
+
+	switch (oxygen_read8(chip, OXYGEN_REVISION) & OXYGEN_PACKAGE_ID_MASK) {
+	case OXYGEN_PACKAGE_ID_8786: i = '6'; break;
+	case OXYGEN_PACKAGE_ID_8787: i = '7'; break;
+	case OXYGEN_PACKAGE_ID_8788: i = '8'; break;
+	default:                     i = '?'; break;
+	}
+	snd_iprintf(buffer, "CMI878%c:\n", i);
+	for (i = 0; i < OXYGEN_IO_SIZE; 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;
+	if (chip->has_ac97_0) {
+		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");
+		}
+	}
+	if (chip->has_ac97_1) {
+		snd_iprintf(buffer, "\nAC97 2:\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, 1, i + j));
+			snd_iprintf(buffer, "\n");
+		}
+	}
+	mutex_unlock(&chip->mutex);
+	if (chip->model.dump_registers)
+		chip->model.dump_registers(chip, buffer);
+}
+
+static void oxygen_proc_init(struct oxygen *chip)
+{
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(chip->card, "oxygen", &entry))
+		snd_info_set_text_ops(entry, chip, oxygen_proc_read);
+}
+
+static const struct pci_device_id *
+oxygen_search_pci_id(struct oxygen *chip, const struct pci_device_id ids[])
+{
+	u16 subdevice;
+
+	/*
+	 * Make sure the EEPROM pins are available, i.e., not used for SPI.
+	 * (This function is called before we initialize or use SPI.)
+	 */
+	oxygen_clear_bits8(chip, OXYGEN_FUNCTION,
+			   OXYGEN_FUNCTION_ENABLE_SPI_4_5);
+	/*
+	 * Read the subsystem device ID directly from the EEPROM, because the
+	 * chip didn't if the first EEPROM word was overwritten.
+	 */
+	subdevice = oxygen_read_eeprom(chip, 2);
+	/* use default ID if EEPROM is missing */
+	if (subdevice == 0xffff && oxygen_read_eeprom(chip, 1) == 0xffff)
+		subdevice = 0x8788;
+	/*
+	 * We use only the subsystem device ID for searching because it is
+	 * unique even without the subsystem vendor ID, which may have been
+	 * overwritten in the EEPROM.
+	 */
+	for (; ids->vendor; ++ids)
+		if (ids->subdevice == subdevice &&
+		    ids->driver_data != BROKEN_EEPROM_DRIVER_DATA)
+			return ids;
+	return NULL;
+}
+
+static void oxygen_restore_eeprom(struct oxygen *chip,
+				  const struct pci_device_id *id)
+{
+	u16 eeprom_id;
+
+	eeprom_id = oxygen_read_eeprom(chip, 0);
+	if (eeprom_id != OXYGEN_EEPROM_ID &&
+	    (eeprom_id != 0xffff || id->subdevice != 0x8788)) {
+		/*
+		 * This function gets called only when a known card model has
+		 * been detected, i.e., we know there is a valid subsystem
+		 * product ID at index 2 in the EEPROM.  Therefore, we have
+		 * been able to deduce the correct subsystem vendor ID, and
+		 * this is enough information to restore the original EEPROM
+		 * contents.
+		 */
+		oxygen_write_eeprom(chip, 1, id->subvendor);
+		oxygen_write_eeprom(chip, 0, OXYGEN_EEPROM_ID);
+
+		oxygen_set_bits8(chip, OXYGEN_MISC,
+				 OXYGEN_MISC_WRITE_PCI_SUBID);
+		pci_write_config_word(chip->pci, PCI_SUBSYSTEM_VENDOR_ID,
+				      id->subvendor);
+		pci_write_config_word(chip->pci, PCI_SUBSYSTEM_ID,
+				      id->subdevice);
+		oxygen_clear_bits8(chip, OXYGEN_MISC,
+				   OXYGEN_MISC_WRITE_PCI_SUBID);
+
+		dev_info(chip->card->dev, "EEPROM ID restored\n");
+	}
+}
+
+static void configure_pcie_bridge(struct pci_dev *pci)
+{
+	enum { PEX811X, PI7C9X110, XIO2001 };
+	static const struct pci_device_id bridge_ids[] = {
+		{ PCI_VDEVICE(PLX, 0x8111), .driver_data = PEX811X },
+		{ PCI_VDEVICE(PLX, 0x8112), .driver_data = PEX811X },
+		{ PCI_DEVICE(0x12d8, 0xe110), .driver_data = PI7C9X110 },
+		{ PCI_VDEVICE(TI, 0x8240), .driver_data = XIO2001 },
+		{ }
+	};
+	struct pci_dev *bridge;
+	const struct pci_device_id *id;
+	u32 tmp;
+
+	if (!pci->bus || !pci->bus->self)
+		return;
+	bridge = pci->bus->self;
+
+	id = pci_match_id(bridge_ids, bridge);
+	if (!id)
+		return;
+
+	switch (id->driver_data) {
+	case PEX811X:	/* PLX PEX8111/PEX8112 PCIe/PCI bridge */
+		pci_read_config_dword(bridge, 0x48, &tmp);
+		tmp |= 1;	/* enable blind prefetching */
+		tmp |= 1 << 11;	/* enable beacon generation */
+		pci_write_config_dword(bridge, 0x48, tmp);
+
+		pci_write_config_dword(bridge, 0x84, 0x0c);
+		pci_read_config_dword(bridge, 0x88, &tmp);
+		tmp &= ~(7 << 27);
+		tmp |= 2 << 27;	/* set prefetch size to 128 bytes */
+		pci_write_config_dword(bridge, 0x88, tmp);
+		break;
+
+	case PI7C9X110:	/* Pericom PI7C9X110 PCIe/PCI bridge */
+		pci_read_config_dword(bridge, 0x40, &tmp);
+		tmp |= 1;	/* park the PCI arbiter to the sound chip */
+		pci_write_config_dword(bridge, 0x40, tmp);
+		break;
+
+	case XIO2001: /* Texas Instruments XIO2001 PCIe/PCI bridge */
+		pci_read_config_dword(bridge, 0xe8, &tmp);
+		tmp &= ~0xf;	/* request length limit: 64 bytes */
+		tmp &= ~(0xf << 8);
+		tmp |= 1 << 8;	/* request count limit: one buffer */
+		pci_write_config_dword(bridge, 0xe8, tmp);
+		break;
+	}
+}
+
+static void oxygen_init(struct oxygen *chip)
+{
+	unsigned int i;
+
+	chip->dac_routing = 1;
+	for (i = 0; i < 8; ++i)
+		chip->dac_volume[i] = chip->model.dac_volume_min;
+	chip->dac_mute = 1;
+	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))
+		oxygen_set_bits8(chip, OXYGEN_MISC,
+				 OXYGEN_MISC_PCI_MEM_W_1_CLOCK);
+
+	i = oxygen_read16(chip, OXYGEN_AC97_CONTROL);
+	chip->has_ac97_0 = (i & OXYGEN_AC97_CODEC_0) != 0;
+	chip->has_ac97_1 = (i & OXYGEN_AC97_CODEC_1) != 0;
+
+	oxygen_write8_masked(chip, OXYGEN_FUNCTION,
+			     OXYGEN_FUNCTION_RESET_CODEC |
+			     chip->model.function_flags,
+			     OXYGEN_FUNCTION_RESET_CODEC |
+			     OXYGEN_FUNCTION_2WIRE_SPI_MASK |
+			     OXYGEN_FUNCTION_ENABLE_SPI_4_5);
+	oxygen_write8(chip, OXYGEN_DMA_STATUS, 0);
+	oxygen_write8(chip, OXYGEN_DMA_PAUSE, 0);
+	oxygen_write8(chip, OXYGEN_PLAY_CHANNELS,
+		      OXYGEN_PLAY_CHANNELS_2 |
+		      OXYGEN_DMA_A_BURST_8 |
+		      OXYGEN_DMA_MULTICH_BURST_8);
+	oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0);
+	oxygen_write8_masked(chip, OXYGEN_MISC,
+			     chip->model.misc_flags,
+			     OXYGEN_MISC_WRITE_PCI_SUBID |
+			     OXYGEN_MISC_REC_C_FROM_SPDIF |
+			     OXYGEN_MISC_REC_B_FROM_AC97 |
+			     OXYGEN_MISC_REC_A_FROM_MULTICH |
+			     OXYGEN_MISC_MIDI);
+	oxygen_write8(chip, OXYGEN_REC_FORMAT,
+		      (OXYGEN_FORMAT_16 << OXYGEN_REC_FORMAT_A_SHIFT) |
+		      (OXYGEN_FORMAT_16 << OXYGEN_REC_FORMAT_B_SHIFT) |
+		      (OXYGEN_FORMAT_16 << OXYGEN_REC_FORMAT_C_SHIFT));
+	oxygen_write8(chip, OXYGEN_PLAY_FORMAT,
+		      (OXYGEN_FORMAT_16 << OXYGEN_SPDIF_FORMAT_SHIFT) |
+		      (OXYGEN_FORMAT_16 << OXYGEN_MULTICH_FORMAT_SHIFT));
+	oxygen_write8(chip, OXYGEN_REC_CHANNELS, OXYGEN_REC_CHANNELS_2_2_2);
+	oxygen_write16(chip, OXYGEN_I2S_MULTICH_FORMAT,
+		       OXYGEN_RATE_48000 |
+		       chip->model.dac_i2s_format |
+		       OXYGEN_I2S_MCLK(chip->model.dac_mclks) |
+		       OXYGEN_I2S_BITS_16 |
+		       OXYGEN_I2S_MASTER |
+		       OXYGEN_I2S_BCLK_64);
+	if (chip->model.device_config & CAPTURE_0_FROM_I2S_1)
+		oxygen_write16(chip, OXYGEN_I2S_A_FORMAT,
+			       OXYGEN_RATE_48000 |
+			       chip->model.adc_i2s_format |
+			       OXYGEN_I2S_MCLK(chip->model.adc_mclks) |
+			       OXYGEN_I2S_BITS_16 |
+			       OXYGEN_I2S_MASTER |
+			       OXYGEN_I2S_BCLK_64);
+	else
+		oxygen_write16(chip, OXYGEN_I2S_A_FORMAT,
+			       OXYGEN_I2S_MASTER |
+			       OXYGEN_I2S_MUTE_MCLK);
+	if (chip->model.device_config & (CAPTURE_0_FROM_I2S_2 |
+					 CAPTURE_2_FROM_I2S_2))
+		oxygen_write16(chip, OXYGEN_I2S_B_FORMAT,
+			       OXYGEN_RATE_48000 |
+			       chip->model.adc_i2s_format |
+			       OXYGEN_I2S_MCLK(chip->model.adc_mclks) |
+			       OXYGEN_I2S_BITS_16 |
+			       OXYGEN_I2S_MASTER |
+			       OXYGEN_I2S_BCLK_64);
+	else
+		oxygen_write16(chip, OXYGEN_I2S_B_FORMAT,
+			       OXYGEN_I2S_MASTER |
+			       OXYGEN_I2S_MUTE_MCLK);
+	if (chip->model.device_config & CAPTURE_3_FROM_I2S_3)
+		oxygen_write16(chip, OXYGEN_I2S_C_FORMAT,
+			       OXYGEN_RATE_48000 |
+			       chip->model.adc_i2s_format |
+			       OXYGEN_I2S_MCLK(chip->model.adc_mclks) |
+			       OXYGEN_I2S_BITS_16 |
+			       OXYGEN_I2S_MASTER |
+			       OXYGEN_I2S_BCLK_64);
+	else
+		oxygen_write16(chip, OXYGEN_I2S_C_FORMAT,
+			       OXYGEN_I2S_MASTER |
+			       OXYGEN_I2S_MUTE_MCLK);
+	oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL,
+			    OXYGEN_SPDIF_OUT_ENABLE |
+			    OXYGEN_SPDIF_LOOPBACK);
+	if (chip->model.device_config & CAPTURE_1_FROM_SPDIF)
+		oxygen_write32_masked(chip, OXYGEN_SPDIF_CONTROL,
+				      OXYGEN_SPDIF_SENSE_MASK |
+				      OXYGEN_SPDIF_LOCK_MASK |
+				      OXYGEN_SPDIF_RATE_MASK |
+				      OXYGEN_SPDIF_LOCK_PAR |
+				      OXYGEN_SPDIF_IN_CLOCK_96,
+				      OXYGEN_SPDIF_SENSE_MASK |
+				      OXYGEN_SPDIF_LOCK_MASK |
+				      OXYGEN_SPDIF_RATE_MASK |
+				      OXYGEN_SPDIF_SENSE_PAR |
+				      OXYGEN_SPDIF_LOCK_PAR |
+				      OXYGEN_SPDIF_IN_CLOCK_MASK);
+	else
+		oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL,
+				    OXYGEN_SPDIF_SENSE_MASK |
+				    OXYGEN_SPDIF_LOCK_MASK |
+				    OXYGEN_SPDIF_RATE_MASK);
+	oxygen_write32(chip, OXYGEN_SPDIF_OUTPUT_BITS, chip->spdif_bits);
+	oxygen_write16(chip, OXYGEN_2WIRE_BUS_STATUS,
+		       OXYGEN_2WIRE_LENGTH_8 |
+		       OXYGEN_2WIRE_INTERRUPT_MASK |
+		       OXYGEN_2WIRE_SPEED_STANDARD);
+	oxygen_clear_bits8(chip, OXYGEN_MPU401_CONTROL, OXYGEN_MPU401_LOOPBACK);
+	oxygen_write8(chip, OXYGEN_GPI_INTERRUPT_MASK, 0);
+	oxygen_write16(chip, OXYGEN_GPIO_INTERRUPT_MASK, 0);
+	oxygen_write16(chip, OXYGEN_PLAY_ROUTING,
+		       OXYGEN_PLAY_MULTICH_I2S_DAC |
+		       OXYGEN_PLAY_SPDIF_SPDIF |
+		       (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+		       (1 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+		       (2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+		       (3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT));
+	oxygen_write8(chip, OXYGEN_REC_ROUTING,
+		      OXYGEN_REC_A_ROUTE_I2S_ADC_1 |
+		      OXYGEN_REC_B_ROUTE_I2S_ADC_2 |
+		      OXYGEN_REC_C_ROUTE_SPDIF);
+	oxygen_write8(chip, OXYGEN_ADC_MONITOR, 0);
+	oxygen_write8(chip, OXYGEN_A_MONITOR_ROUTING,
+		      (0 << OXYGEN_A_MONITOR_ROUTE_0_SHIFT) |
+		      (1 << OXYGEN_A_MONITOR_ROUTE_1_SHIFT) |
+		      (2 << OXYGEN_A_MONITOR_ROUTE_2_SHIFT) |
+		      (3 << OXYGEN_A_MONITOR_ROUTE_3_SHIFT));
+
+	if (chip->has_ac97_0 | chip->has_ac97_1)
+		oxygen_write8(chip, OXYGEN_AC97_INTERRUPT_MASK,
+			      OXYGEN_AC97_INT_READ_DONE |
+			      OXYGEN_AC97_INT_WRITE_DONE);
+	else
+		oxygen_write8(chip, OXYGEN_AC97_INTERRUPT_MASK, 0);
+	oxygen_write32(chip, OXYGEN_AC97_OUT_CONFIG, 0);
+	oxygen_write32(chip, OXYGEN_AC97_IN_CONFIG, 0);
+	if (!(chip->has_ac97_0 | chip->has_ac97_1))
+		oxygen_set_bits16(chip, OXYGEN_AC97_CONTROL,
+				  OXYGEN_AC97_CLOCK_DISABLE);
+	if (!chip->has_ac97_0) {
+		oxygen_set_bits16(chip, OXYGEN_AC97_CONTROL,
+				  OXYGEN_AC97_NO_CODEC_0);
+	} else {
+		oxygen_write_ac97(chip, 0, AC97_RESET, 0);
+		msleep(1);
+		oxygen_ac97_set_bits(chip, 0, CM9780_GPIO_SETUP,
+				     CM9780_GPIO0IO | CM9780_GPIO1IO);
+		oxygen_ac97_set_bits(chip, 0, CM9780_MIXER,
+				     CM9780_BSTSEL | CM9780_STRO_MIC |
+				     CM9780_MIX2FR | CM9780_PCBSW);
+		oxygen_ac97_set_bits(chip, 0, CM9780_JACK,
+				     CM9780_RSOE | CM9780_CBOE |
+				     CM9780_SSOE | CM9780_FROE |
+				     CM9780_MIC2MIC | CM9780_LI2LI);
+		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, CM9780_GPIO_STATUS,
+				       CM9780_GPO0);
+		/* 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);
+	}
+	if (chip->has_ac97_1) {
+		oxygen_set_bits32(chip, OXYGEN_AC97_OUT_CONFIG,
+				  OXYGEN_AC97_CODEC1_SLOT3 |
+				  OXYGEN_AC97_CODEC1_SLOT4);
+		oxygen_write_ac97(chip, 1, AC97_RESET, 0);
+		msleep(1);
+		oxygen_write_ac97(chip, 1, AC97_MASTER, 0x0000);
+		oxygen_write_ac97(chip, 1, AC97_HEADPHONE, 0x8000);
+		oxygen_write_ac97(chip, 1, AC97_PC_BEEP, 0x8000);
+		oxygen_write_ac97(chip, 1, AC97_MIC, 0x8808);
+		oxygen_write_ac97(chip, 1, AC97_LINE, 0x8808);
+		oxygen_write_ac97(chip, 1, AC97_CD, 0x8808);
+		oxygen_write_ac97(chip, 1, AC97_VIDEO, 0x8808);
+		oxygen_write_ac97(chip, 1, AC97_AUX, 0x8808);
+		oxygen_write_ac97(chip, 1, AC97_PCM, 0x0808);
+		oxygen_write_ac97(chip, 1, AC97_REC_SEL, 0x0000);
+		oxygen_write_ac97(chip, 1, AC97_REC_GAIN, 0x0000);
+		oxygen_ac97_set_bits(chip, 1, 0x6a, 0x0040);
+	}
+}
+
+static void oxygen_shutdown(struct oxygen *chip)
+{
+	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);
+}
+
+static void oxygen_card_free(struct snd_card *card)
+{
+	struct oxygen *chip = card->private_data;
+
+	oxygen_shutdown(chip);
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	flush_work(&chip->spdif_input_bits_work);
+	flush_work(&chip->gpio_work);
+	chip->model.cleanup(chip);
+	kfree(chip->model_data);
+	mutex_destroy(&chip->mutex);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+}
+
+int oxygen_pci_probe(struct pci_dev *pci, int index, char *id,
+		     struct module *owner,
+		     const struct pci_device_id *ids,
+		     int (*get_model)(struct oxygen *chip,
+				      const struct pci_device_id *id
+				     )
+		    )
+{
+	struct snd_card *card;
+	struct oxygen *chip;
+	const struct pci_device_id *pci_id;
+	int err;
+
+	err = snd_card_new(&pci->dev, index, id, owner,
+			   sizeof(*chip), &card);
+	if (err < 0)
+		return err;
+
+	chip = card->private_data;
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+	spin_lock_init(&chip->reg_lock);
+	mutex_init(&chip->mutex);
+	INIT_WORK(&chip->spdif_input_bits_work,
+		  oxygen_spdif_input_bits_changed);
+	INIT_WORK(&chip->gpio_work, oxygen_gpio_changed);
+	init_waitqueue_head(&chip->ac97_waitqueue);
+
+	err = pci_enable_device(pci);
+	if (err < 0)
+		goto err_card;
+
+	err = pci_request_regions(pci, DRIVER);
+	if (err < 0) {
+		dev_err(card->dev, "cannot reserve PCI resources\n");
+		goto err_pci_enable;
+	}
+
+	if (!(pci_resource_flags(pci, 0) & IORESOURCE_IO) ||
+	    pci_resource_len(pci, 0) < OXYGEN_IO_SIZE) {
+		dev_err(card->dev, "invalid PCI I/O range\n");
+		err = -ENXIO;
+		goto err_pci_regions;
+	}
+	chip->addr = pci_resource_start(pci, 0);
+
+	pci_id = oxygen_search_pci_id(chip, ids);
+	if (!pci_id) {
+		err = -ENODEV;
+		goto err_pci_regions;
+	}
+	oxygen_restore_eeprom(chip, pci_id);
+	err = get_model(chip, pci_id);
+	if (err < 0)
+		goto err_pci_regions;
+
+	if (chip->model.model_data_size) {
+		chip->model_data = kzalloc(chip->model.model_data_size,
+					   GFP_KERNEL);
+		if (!chip->model_data) {
+			err = -ENOMEM;
+			goto err_pci_regions;
+		}
+	}
+
+	pci_set_master(pci);
+	card->private_free = oxygen_card_free;
+
+	configure_pcie_bridge(pci);
+	oxygen_init(chip);
+	chip->model.init(chip);
+
+	err = request_irq(pci->irq, oxygen_interrupt, IRQF_SHARED,
+			  KBUILD_MODNAME, chip);
+	if (err < 0) {
+		dev_err(card->dev, "cannot grab interrupt %d\n", pci->irq);
+		goto err_card;
+	}
+	chip->irq = pci->irq;
+
+	strcpy(card->driver, chip->model.chip);
+	strcpy(card->shortname, chip->model.shortname);
+	sprintf(card->longname, "%s at %#lx, irq %i",
+		chip->model.longname, chip->addr, chip->irq);
+	strcpy(card->mixername, chip->model.chip);
+	snd_component_add(card, chip->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 (chip->model.device_config & (MIDI_OUTPUT | MIDI_INPUT)) {
+		unsigned int info_flags =
+				MPU401_INFO_INTEGRATED | MPU401_INFO_IRQ_HOOK;
+		if (chip->model.device_config & MIDI_OUTPUT)
+			info_flags |= MPU401_INFO_OUTPUT;
+		if (chip->model.device_config & MIDI_INPUT)
+			info_flags |= MPU401_INFO_INPUT;
+		err = snd_mpu401_uart_new(card, 0, MPU401_HW_CMIPCI,
+					  chip->addr + OXYGEN_MPU401,
+					  info_flags, -1, &chip->midi);
+		if (err < 0)
+			goto err_card;
+	}
+
+	oxygen_proc_init(chip);
+
+	spin_lock_irq(&chip->reg_lock);
+	if (chip->model.device_config & CAPTURE_1_FROM_SPDIF)
+		chip->interrupt_mask |= OXYGEN_INT_SPDIF_IN_DETECT;
+	if (chip->has_ac97_0 | chip->has_ac97_1)
+		chip->interrupt_mask |= OXYGEN_INT_AC97;
+	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 oxygen_pci_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+}
+EXPORT_SYMBOL(oxygen_pci_remove);
+
+#ifdef CONFIG_PM_SLEEP
+static int oxygen_pci_suspend(struct device *dev)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct oxygen *chip = card->private_data;
+	unsigned int i, saved_interrupt_mask;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+	for (i = 0; i < PCM_COUNT; ++i)
+		snd_pcm_suspend(chip->streams[i]);
+
+	if (chip->model.suspend)
+		chip->model.suspend(chip);
+
+	spin_lock_irq(&chip->reg_lock);
+	saved_interrupt_mask = chip->interrupt_mask;
+	chip->interrupt_mask = 0;
+	oxygen_write16(chip, OXYGEN_DMA_STATUS, 0);
+	oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0);
+	spin_unlock_irq(&chip->reg_lock);
+
+	synchronize_irq(chip->irq);
+	flush_work(&chip->spdif_input_bits_work);
+	flush_work(&chip->gpio_work);
+	chip->interrupt_mask = saved_interrupt_mask;
+	return 0;
+}
+
+static const u32 registers_to_restore[OXYGEN_IO_SIZE / 32] = {
+	0xffffffff, 0x00ff077f, 0x00011d08, 0x007f00ff,
+	0x00300000, 0x00000fe4, 0x0ff7001f, 0x00000000
+};
+static const u32 ac97_registers_to_restore[2][0x40 / 32] = {
+	{ 0x18284fa2, 0x03060000 },
+	{ 0x00007fa6, 0x00200000 }
+};
+
+static inline int is_bit_set(const u32 *bitmap, unsigned int bit)
+{
+	return bitmap[bit / 32] & (1 << (bit & 31));
+}
+
+static void oxygen_restore_ac97(struct oxygen *chip, unsigned int codec)
+{
+	unsigned int i;
+
+	oxygen_write_ac97(chip, codec, AC97_RESET, 0);
+	msleep(1);
+	for (i = 1; i < 0x40; ++i)
+		if (is_bit_set(ac97_registers_to_restore[codec], i))
+			oxygen_write_ac97(chip, codec, i * 2,
+					  chip->saved_ac97_registers[codec][i]);
+}
+
+static int oxygen_pci_resume(struct device *dev)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct oxygen *chip = card->private_data;
+	unsigned int i;
+
+	oxygen_write16(chip, OXYGEN_DMA_STATUS, 0);
+	oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0);
+	for (i = 0; i < OXYGEN_IO_SIZE; ++i)
+		if (is_bit_set(registers_to_restore, i))
+			oxygen_write8(chip, i, chip->saved_registers._8[i]);
+	if (chip->has_ac97_0)
+		oxygen_restore_ac97(chip, 0);
+	if (chip->has_ac97_1)
+		oxygen_restore_ac97(chip, 1);
+
+	if (chip->model.resume)
+		chip->model.resume(chip);
+
+	oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+
+SIMPLE_DEV_PM_OPS(oxygen_pci_pm, oxygen_pci_suspend, oxygen_pci_resume);
+EXPORT_SYMBOL(oxygen_pci_pm);
+#endif /* CONFIG_PM_SLEEP */
+
+void oxygen_pci_shutdown(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct oxygen *chip = card->private_data;
+
+	oxygen_shutdown(chip);
+	chip->model.cleanup(chip);
+}
+EXPORT_SYMBOL(oxygen_pci_shutdown);
diff --git a/sound/pci/oxygen/oxygen_mixer.c b/sound/pci/oxygen/oxygen_mixer.c
new file mode 100644
index 0000000..81af21a
--- /dev/null
+++ b/sound/pci/oxygen/oxygen_mixer.c
@@ -0,0 +1,1139 @@
+/*
+ * 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 <linux/mutex.h>
+#include <sound/ac97_codec.h>
+#include <sound/asoundef.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include "oxygen.h"
+#include "cm9780.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 = chip->model.dac_channels_mixer;
+	info->value.integer.min = chip->model.dac_volume_min;
+	info->value.integer.max = chip->model.dac_volume_max;
+	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 < chip->model.dac_channels_mixer; ++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 < chip->model.dac_channels_mixer; ++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 unsigned int upmix_item_count(struct oxygen *chip)
+{
+	if (chip->model.dac_channels_pcm < 8)
+		return 2;
+	else if (chip->model.update_center_lfe_mix)
+		return 5;
+	else
+		return 3;
+}
+
+static int upmix_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)
+{
+	static const char *const names[5] = {
+		"Front",
+		"Front+Surround",
+		"Front+Surround+Back",
+		"Front+Surround+Center/LFE",
+		"Front+Surround+Center/LFE+Back",
+	};
+	struct oxygen *chip = ctl->private_data;
+	unsigned int count = upmix_item_count(chip);
+
+	return snd_ctl_enum_info(info, 1, count, names);
+}
+
+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)
+{
+	/* DAC 0: front, DAC 1: surround, DAC 2: center/LFE, DAC 3: back */
+	static const unsigned int reg_values[5] = {
+		/* stereo -> front */
+		(0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+		(1 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+		(2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+		(3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT),
+		/* stereo -> front+surround */
+		(0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+		(2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+		(3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT),
+		/* stereo -> front+surround+back */
+		(0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+		(2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT),
+		/* stereo -> front+surround+center/LFE */
+		(0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+		(3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT),
+		/* stereo -> front+surround+center/LFE+back */
+		(0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT),
+	};
+	u8 channels;
+	unsigned int reg_value;
+
+	channels = oxygen_read8(chip, OXYGEN_PLAY_CHANNELS) &
+		OXYGEN_PLAY_CHANNELS_MASK;
+	if (channels == OXYGEN_PLAY_CHANNELS_2)
+		reg_value = reg_values[chip->dac_routing];
+	else if (channels == OXYGEN_PLAY_CHANNELS_8)
+		/* in 7.1 mode, "rear" channels go to the "back" jack */
+		reg_value = (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+			    (3 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+			    (2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+			    (1 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT);
+	else
+		reg_value = (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+			    (1 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+			    (2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+			    (3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT);
+	if (chip->model.adjust_dac_routing)
+		reg_value = chip->model.adjust_dac_routing(chip, reg_value);
+	oxygen_write16_masked(chip, OXYGEN_PLAY_ROUTING, reg_value,
+			      OXYGEN_PLAY_DAC0_SOURCE_MASK |
+			      OXYGEN_PLAY_DAC1_SOURCE_MASK |
+			      OXYGEN_PLAY_DAC2_SOURCE_MASK |
+			      OXYGEN_PLAY_DAC3_SOURCE_MASK);
+	if (chip->model.update_center_lfe_mix)
+		chip->model.update_center_lfe_mix(chip, chip->dac_routing > 2);
+}
+EXPORT_SYMBOL(oxygen_update_dac_routing);
+
+static int upmix_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	unsigned int count = upmix_item_count(chip);
+	int changed;
+
+	if (value->value.enumerated.item[0] >= count)
+		return -EINVAL;
+	mutex_lock(&chip->mutex);
+	changed = value->value.enumerated.item[0] != chip->dac_routing;
+	if (changed) {
+		chip->dac_routing = value->value.enumerated.item[0];
+		oxygen_update_dac_routing(chip);
+	}
+	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 IEC958_AES3_CON_FS_88200 << OXYGEN_SPDIF_CS_RATE_SHIFT;
+	case OXYGEN_RATE_96000:
+		return IEC958_AES3_CON_FS_96000 << OXYGEN_SPDIF_CS_RATE_SHIFT;
+	case OXYGEN_RATE_176400:
+		return IEC958_AES3_CON_FS_176400 << OXYGEN_SPDIF_CS_RATE_SHIFT;
+	case OXYGEN_RATE_192000:
+		return IEC958_AES3_CON_FS_192000 << 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 & ~OXYGEN_PLAY_SPDIF_MASK)
+			| OXYGEN_PLAY_SPDIF_SPDIF;
+		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 & ~OXYGEN_PLAY_SPDIF_MASK)
+			| OXYGEN_PLAY_SPDIF_MULTICH_01;
+		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 spdif_bit_switch_get(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u32 bit = ctl->private_value;
+
+	value->value.integer.value[0] =
+		!!(oxygen_read32(chip, OXYGEN_SPDIF_CONTROL) & bit);
+	return 0;
+}
+
+static int spdif_bit_switch_put(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u32 bit = ctl->private_value;
+	u32 oldreg, newreg;
+	int changed;
+
+	spin_lock_irq(&chip->reg_lock);
+	oldreg = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL);
+	if (value->value.integer.value[0])
+		newreg = oldreg | bit;
+	else
+		newreg = oldreg & ~bit;
+	changed = newreg != oldreg;
+	if (changed)
+		oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, newreg);
+	spin_unlock_irq(&chip->reg_lock);
+	return changed;
+}
+
+static int monitor_volume_info(struct snd_kcontrol *ctl,
+			       struct snd_ctl_elem_info *info)
+{
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = 1;
+	info->value.integer.min = 0;
+	info->value.integer.max = 1;
+	return 0;
+}
+
+static int monitor_get(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u8 bit = ctl->private_value;
+	int invert = ctl->private_value & (1 << 8);
+
+	value->value.integer.value[0] =
+		!!invert ^ !!(oxygen_read8(chip, OXYGEN_ADC_MONITOR) & bit);
+	return 0;
+}
+
+static int monitor_put(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u8 bit = ctl->private_value;
+	int invert = ctl->private_value & (1 << 8);
+	u8 oldreg, newreg;
+	int changed;
+
+	spin_lock_irq(&chip->reg_lock);
+	oldreg = oxygen_read8(chip, OXYGEN_ADC_MONITOR);
+	if ((!!value->value.integer.value[0] ^ !!invert) != 0)
+		newreg = oldreg | bit;
+	else
+		newreg = oldreg & ~bit;
+	changed = newreg != oldreg;
+	if (changed)
+		oxygen_write8(chip, OXYGEN_ADC_MONITOR, newreg);
+	spin_unlock_irq(&chip->reg_lock);
+	return changed;
+}
+
+static int ac97_switch_get(struct snd_kcontrol *ctl,
+			   struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	unsigned int codec = (ctl->private_value >> 24) & 1;
+	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, codec, 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 void mute_ac97_ctl(struct oxygen *chip, unsigned int control)
+{
+	unsigned int priv_idx;
+	u16 value;
+
+	if (!chip->controls[control])
+		return;
+	priv_idx = chip->controls[control]->private_value & 0xff;
+	value = oxygen_read_ac97(chip, 0, priv_idx);
+	if (!(value & 0x8000)) {
+		oxygen_write_ac97(chip, 0, priv_idx, value | 0x8000);
+		if (chip->model.ac97_switch)
+			chip->model.ac97_switch(chip, priv_idx, 0x8000);
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &chip->controls[control]->id);
+	}
+}
+
+static int ac97_switch_put(struct snd_kcontrol *ctl,
+			   struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	unsigned int codec = (ctl->private_value >> 24) & 1;
+	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, codec, 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, codec, index, newreg);
+		if (codec == 0 && chip->model.ac97_switch)
+			chip->model.ac97_switch(chip, index, newreg & 0x8000);
+		if (index == AC97_LINE) {
+			oxygen_write_ac97_masked(chip, 0, CM9780_GPIO_STATUS,
+						 newreg & 0x8000 ?
+						 CM9780_GPO0 : 0, CM9780_GPO0);
+			if (!(newreg & 0x8000)) {
+				mute_ac97_ctl(chip, CONTROL_MIC_CAPTURE_SWITCH);
+				mute_ac97_ctl(chip, CONTROL_CD_CAPTURE_SWITCH);
+				mute_ac97_ctl(chip, CONTROL_AUX_CAPTURE_SWITCH);
+			}
+		} else if ((index == AC97_MIC || index == AC97_CD ||
+			    index == AC97_VIDEO || index == AC97_AUX) &&
+			   bitnr == 15 && !(newreg & 0x8000)) {
+			mute_ac97_ctl(chip, CONTROL_LINE_CAPTURE_SWITCH);
+			oxygen_write_ac97_masked(chip, 0, CM9780_GPIO_STATUS,
+						 CM9780_GPO0, CM9780_GPO0);
+		}
+	}
+	mutex_unlock(&chip->mutex);
+	return change;
+}
+
+static int ac97_volume_info(struct snd_kcontrol *ctl,
+			    struct snd_ctl_elem_info *info)
+{
+	int stereo = (ctl->private_value >> 16) & 1;
+
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = stereo ? 2 : 1;
+	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 codec = (ctl->private_value >> 24) & 1;
+	int stereo = (ctl->private_value >> 16) & 1;
+	unsigned int index = ctl->private_value & 0xff;
+	u16 reg;
+
+	mutex_lock(&chip->mutex);
+	reg = oxygen_read_ac97(chip, codec, index);
+	mutex_unlock(&chip->mutex);
+	if (!stereo) {
+		value->value.integer.value[0] = 31 - (reg & 0x1f);
+	} else {
+		value->value.integer.value[0] = 31 - ((reg >> 8) & 0x1f);
+		value->value.integer.value[1] = 31 - (reg & 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 codec = (ctl->private_value >> 24) & 1;
+	int stereo = (ctl->private_value >> 16) & 1;
+	unsigned int index = ctl->private_value & 0xff;
+	u16 oldreg, newreg;
+	int change;
+
+	mutex_lock(&chip->mutex);
+	oldreg = oxygen_read_ac97(chip, codec, index);
+	if (!stereo) {
+		newreg = oldreg & ~0x1f;
+		newreg |= 31 - (value->value.integer.value[0] & 0x1f);
+	} else {
+		newreg = oldreg & ~0x1f1f;
+		newreg |= (31 - (value->value.integer.value[0] & 0x1f)) << 8;
+		newreg |= 31 - (value->value.integer.value[1] & 0x1f);
+	}
+	change = newreg != oldreg;
+	if (change)
+		oxygen_write_ac97(chip, codec, index, newreg);
+	mutex_unlock(&chip->mutex);
+	return change;
+}
+
+static int mic_fmic_source_info(struct snd_kcontrol *ctl,
+			   struct snd_ctl_elem_info *info)
+{
+	static const char *const names[] = { "Mic Jack", "Front Panel" };
+
+	return snd_ctl_enum_info(info, 1, 2, names);
+}
+
+static int mic_fmic_source_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] =
+		!!(oxygen_read_ac97(chip, 0, CM9780_JACK) & CM9780_FMIC2MIC);
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int mic_fmic_source_put(struct snd_kcontrol *ctl,
+			       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u16 oldreg, newreg;
+	int change;
+
+	mutex_lock(&chip->mutex);
+	oldreg = oxygen_read_ac97(chip, 0, CM9780_JACK);
+	if (value->value.enumerated.item[0])
+		newreg = oldreg | CM9780_FMIC2MIC;
+	else
+		newreg = oldreg & ~CM9780_FMIC2MIC;
+	change = newreg != oldreg;
+	if (change)
+		oxygen_write_ac97(chip, 0, CM9780_JACK, newreg);
+	mutex_unlock(&chip->mutex);
+	return change;
+}
+
+static int ac97_fp_rec_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 = 7;
+	return 0;
+}
+
+static int ac97_fp_rec_volume_get(struct snd_kcontrol *ctl,
+				  struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u16 reg;
+
+	mutex_lock(&chip->mutex);
+	reg = oxygen_read_ac97(chip, 1, AC97_REC_GAIN);
+	mutex_unlock(&chip->mutex);
+	value->value.integer.value[0] = reg & 7;
+	value->value.integer.value[1] = (reg >> 8) & 7;
+	return 0;
+}
+
+static int ac97_fp_rec_volume_put(struct snd_kcontrol *ctl,
+				  struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u16 oldreg, newreg;
+	int change;
+
+	mutex_lock(&chip->mutex);
+	oldreg = oxygen_read_ac97(chip, 1, AC97_REC_GAIN);
+	newreg = oldreg & ~0x0707;
+	newreg = newreg | (value->value.integer.value[0] & 7);
+	newreg = newreg | ((value->value.integer.value[0] & 7) << 8);
+	change = newreg != oldreg;
+	if (change)
+		oxygen_write_ac97(chip, 1, AC97_REC_GAIN, newreg);
+	mutex_unlock(&chip->mutex);
+	return change;
+}
+
+#define AC97_SWITCH(xname, codec, 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 = ((codec) << 24) | ((invert) << 16) | \
+				 ((bitnr) << 8) | (index), \
+	}
+#define AC97_VOLUME(xname, codec, index, stereo) { \
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+		.name = xname, \
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+			  SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+		.info = ac97_volume_info, \
+		.get = ac97_volume_get, \
+		.put = ac97_volume_put, \
+		.tlv = { .p = ac97_db_scale, }, \
+		.private_value = ((codec) << 24) | ((stereo) << 16) | (index), \
+	}
+
+static DECLARE_TLV_DB_SCALE(monitor_db_scale, -600, 600, 0);
+static DECLARE_TLV_DB_SCALE(ac97_db_scale, -3450, 150, 0);
+static DECLARE_TLV_DB_SCALE(ac97_rec_db_scale, 0, 150, 0);
+
+static const struct snd_kcontrol_new controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Volume",
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+		.info = dac_volume_info,
+		.get = dac_volume_get,
+		.put = dac_volume_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master 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,
+	},
+};
+
+static const struct snd_kcontrol_new spdif_output_controls[] = {
+	{
+		.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,
+	},
+};
+
+static const struct snd_kcontrol_new spdif_input_controls[] = {
+	{
+		.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,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("Loopback ", NONE, SWITCH),
+		.info = snd_ctl_boolean_mono_info,
+		.get = spdif_bit_switch_get,
+		.put = spdif_bit_switch_put,
+		.private_value = OXYGEN_SPDIF_LOOPBACK,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("Validity Check ",CAPTURE,SWITCH),
+		.info = snd_ctl_boolean_mono_info,
+		.get = spdif_bit_switch_get,
+		.put = spdif_bit_switch_put,
+		.private_value = OXYGEN_SPDIF_SPDVALID,
+	},
+};
+
+static const struct {
+	unsigned int pcm_dev;
+	struct snd_kcontrol_new controls[2];
+} monitor_controls[] = {
+	{
+		.pcm_dev = CAPTURE_0_FROM_I2S_1,
+		.controls = {
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Analog Input Monitor Playback Switch",
+				.info = snd_ctl_boolean_mono_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_A,
+			},
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Analog Input Monitor Playback Volume",
+				.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+					  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+				.info = monitor_volume_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_A_HALF_VOL
+						| (1 << 8),
+				.tlv = { .p = monitor_db_scale, },
+			},
+		},
+	},
+	{
+		.pcm_dev = CAPTURE_0_FROM_I2S_2,
+		.controls = {
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Analog Input Monitor Playback Switch",
+				.info = snd_ctl_boolean_mono_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_B,
+			},
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Analog Input Monitor Playback Volume",
+				.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+					  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+				.info = monitor_volume_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_B_HALF_VOL
+						| (1 << 8),
+				.tlv = { .p = monitor_db_scale, },
+			},
+		},
+	},
+	{
+		.pcm_dev = CAPTURE_2_FROM_I2S_2,
+		.controls = {
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Analog Input Monitor Playback Switch",
+				.index = 1,
+				.info = snd_ctl_boolean_mono_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_B,
+			},
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Analog Input Monitor Playback Volume",
+				.index = 1,
+				.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+					  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+				.info = monitor_volume_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_B_HALF_VOL
+						| (1 << 8),
+				.tlv = { .p = monitor_db_scale, },
+			},
+		},
+	},
+	{
+		.pcm_dev = CAPTURE_3_FROM_I2S_3,
+		.controls = {
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Analog Input Monitor Playback Switch",
+				.index = 2,
+				.info = snd_ctl_boolean_mono_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_C,
+			},
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Analog Input Monitor Playback Volume",
+				.index = 2,
+				.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+					  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+				.info = monitor_volume_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_C_HALF_VOL
+						| (1 << 8),
+				.tlv = { .p = monitor_db_scale, },
+			},
+		},
+	},
+	{
+		.pcm_dev = CAPTURE_1_FROM_SPDIF,
+		.controls = {
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Digital Input Monitor Playback Switch",
+				.info = snd_ctl_boolean_mono_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_C,
+			},
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Digital Input Monitor Playback Volume",
+				.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+					  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+				.info = monitor_volume_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_C_HALF_VOL
+						| (1 << 8),
+				.tlv = { .p = monitor_db_scale, },
+			},
+		},
+	},
+};
+
+static const struct snd_kcontrol_new ac97_controls[] = {
+	AC97_VOLUME("Mic Capture Volume", 0, AC97_MIC, 0),
+	AC97_SWITCH("Mic Capture Switch", 0, AC97_MIC, 15, 1),
+	AC97_SWITCH("Mic Boost (+20dB)", 0, AC97_MIC, 6, 0),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Mic Source Capture Enum",
+		.info = mic_fmic_source_info,
+		.get = mic_fmic_source_get,
+		.put = mic_fmic_source_put,
+	},
+	AC97_SWITCH("Line Capture Switch", 0, AC97_LINE, 15, 1),
+	AC97_VOLUME("CD Capture Volume", 0, AC97_CD, 1),
+	AC97_SWITCH("CD Capture Switch", 0, AC97_CD, 15, 1),
+	AC97_VOLUME("Aux Capture Volume", 0, AC97_AUX, 1),
+	AC97_SWITCH("Aux Capture Switch", 0, AC97_AUX, 15, 1),
+};
+
+static const struct snd_kcontrol_new ac97_fp_controls[] = {
+	AC97_VOLUME("Front Panel Playback Volume", 1, AC97_HEADPHONE, 1),
+	AC97_SWITCH("Front Panel Playback Switch", 1, AC97_HEADPHONE, 15, 1),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Front Panel Capture Volume",
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+		.info = ac97_fp_rec_volume_info,
+		.get = ac97_fp_rec_volume_get,
+		.put = ac97_fp_rec_volume_put,
+		.tlv = { .p = ac97_rec_db_scale, },
+	},
+	AC97_SWITCH("Front Panel Capture Switch", 1, AC97_REC_GAIN, 15, 1),
+};
+
+static void oxygen_any_ctl_free(struct snd_kcontrol *ctl)
+{
+	struct oxygen *chip = ctl->private_data;
+	unsigned int i;
+
+	/* I'm too lazy to write a function for each control :-) */
+	for (i = 0; i < ARRAY_SIZE(chip->controls); ++i)
+		chip->controls[i] = NULL;
+}
+
+static int add_controls(struct oxygen *chip,
+			const struct snd_kcontrol_new controls[],
+			unsigned int count)
+{
+	static const char *const known_ctl_names[CONTROL_COUNT] = {
+		[CONTROL_SPDIF_PCM] =
+			SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM),
+		[CONTROL_SPDIF_INPUT_BITS] =
+			SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+		[CONTROL_MIC_CAPTURE_SWITCH] = "Mic Capture Switch",
+		[CONTROL_LINE_CAPTURE_SWITCH] = "Line Capture Switch",
+		[CONTROL_CD_CAPTURE_SWITCH] = "CD Capture Switch",
+		[CONTROL_AUX_CAPTURE_SWITCH] = "Aux Capture Switch",
+	};
+	unsigned int i;
+	struct snd_kcontrol_new template;
+	struct snd_kcontrol *ctl;
+	int j, err;
+
+	for (i = 0; i < count; ++i) {
+		template = controls[i];
+		if (chip->model.control_filter) {
+			err = chip->model.control_filter(&template);
+			if (err < 0)
+				return err;
+			if (err == 1)
+				continue;
+		}
+		if (!strcmp(template.name, "Stereo Upmixing") &&
+		    chip->model.dac_channels_pcm == 2)
+			continue;
+		if (!strcmp(template.name, "Mic Source Capture Enum") &&
+		    !(chip->model.device_config & AC97_FMIC_SWITCH))
+			continue;
+		if (!strncmp(template.name, "CD Capture ", 11) &&
+		    !(chip->model.device_config & AC97_CD_INPUT))
+			continue;
+		if (!strcmp(template.name, "Master Playback Volume") &&
+		    chip->model.dac_tlv) {
+			template.tlv.p = chip->model.dac_tlv;
+			template.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+		}
+		ctl = snd_ctl_new1(&template, chip);
+		if (!ctl)
+			return -ENOMEM;
+		err = snd_ctl_add(chip->card, ctl);
+		if (err < 0)
+			return err;
+		j = match_string(known_ctl_names, CONTROL_COUNT, ctl->id.name);
+		if (j >= 0) {
+			chip->controls[j] = ctl;
+			ctl->private_free = oxygen_any_ctl_free;
+		}
+	}
+	return 0;
+}
+
+int oxygen_mixer_init(struct oxygen *chip)
+{
+	unsigned int i;
+	int err;
+
+	err = add_controls(chip, controls, ARRAY_SIZE(controls));
+	if (err < 0)
+		return err;
+	if (chip->model.device_config & PLAYBACK_1_TO_SPDIF) {
+		err = add_controls(chip, spdif_output_controls,
+				   ARRAY_SIZE(spdif_output_controls));
+		if (err < 0)
+			return err;
+	}
+	if (chip->model.device_config & CAPTURE_1_FROM_SPDIF) {
+		err = add_controls(chip, spdif_input_controls,
+				   ARRAY_SIZE(spdif_input_controls));
+		if (err < 0)
+			return err;
+	}
+	for (i = 0; i < ARRAY_SIZE(monitor_controls); ++i) {
+		if (!(chip->model.device_config & monitor_controls[i].pcm_dev))
+			continue;
+		err = add_controls(chip, monitor_controls[i].controls,
+				   ARRAY_SIZE(monitor_controls[i].controls));
+		if (err < 0)
+			return err;
+	}
+	if (chip->has_ac97_0) {
+		err = add_controls(chip, ac97_controls,
+				   ARRAY_SIZE(ac97_controls));
+		if (err < 0)
+			return err;
+	}
+	if (chip->has_ac97_1) {
+		err = add_controls(chip, ac97_fp_controls,
+				   ARRAY_SIZE(ac97_fp_controls));
+		if (err < 0)
+			return err;
+	}
+	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
index 0000000..042a2439
--- /dev/null
+++ b/sound/pci/oxygen/oxygen_pcm.c
@@ -0,0 +1,807 @@
+/*
+ * 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 <linux/pci.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "oxygen.h"
+
+/* most DMA channels have a 16-bit counter for 32-bit words */
+#define BUFFER_BYTES_MAX		((1 << 16) * 4)
+/* the multichannel DMA channel has a 24-bit counter */
+#define BUFFER_BYTES_MAX_MULTICH	((1 << 24) * 4)
+
+#define FIFO_BYTES			256
+#define FIFO_BYTES_MULTICH		1024
+
+#define PERIOD_BYTES_MIN		64
+
+#define DEFAULT_BUFFER_BYTES		(BUFFER_BYTES_MAX / 2)
+#define DEFAULT_BUFFER_BYTES_MULTICH	(1024 * 1024)
+
+static const struct snd_pcm_hardware oxygen_stereo_hardware = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START |
+		SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+	.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 = BUFFER_BYTES_MAX,
+	.period_bytes_min = PERIOD_BYTES_MIN,
+	.period_bytes_max = BUFFER_BYTES_MAX,
+	.periods_min = 1,
+	.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
+	.fifo_size = FIFO_BYTES,
+};
+static const struct snd_pcm_hardware oxygen_multichannel_hardware = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START |
+		SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+	.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 = BUFFER_BYTES_MAX_MULTICH,
+	.period_bytes_min = PERIOD_BYTES_MIN,
+	.period_bytes_max = BUFFER_BYTES_MAX_MULTICH,
+	.periods_min = 1,
+	.periods_max = BUFFER_BYTES_MAX_MULTICH / PERIOD_BYTES_MIN,
+	.fifo_size = FIFO_BYTES_MULTICH,
+};
+static const struct snd_pcm_hardware oxygen_ac97_hardware = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START |
+		SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+	.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 = BUFFER_BYTES_MAX,
+	.period_bytes_min = PERIOD_BYTES_MIN,
+	.period_bytes_max = BUFFER_BYTES_MAX,
+	.periods_min = 1,
+	.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
+	.fifo_size = FIFO_BYTES,
+};
+
+static const struct snd_pcm_hardware *const oxygen_hardware[PCM_COUNT] = {
+	[PCM_A] = &oxygen_stereo_hardware,
+	[PCM_B] = &oxygen_stereo_hardware,
+	[PCM_C] = &oxygen_stereo_hardware,
+	[PCM_SPDIF] = &oxygen_stereo_hardware,
+	[PCM_MULTICH] = &oxygen_multichannel_hardware,
+	[PCM_AC97] = &oxygen_ac97_hardware,
+};
+
+static inline unsigned int
+oxygen_substream_channel(struct snd_pcm_substream *substream)
+{
+	return (unsigned int)(uintptr_t)substream->runtime->private_data;
+}
+
+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 *)(uintptr_t)channel;
+	if (channel == PCM_B && chip->has_ac97_1 &&
+	    (chip->model.device_config & CAPTURE_2_FROM_AC97_1))
+		runtime->hw = oxygen_ac97_hardware;
+	else
+		runtime->hw = *oxygen_hardware[channel];
+	switch (channel) {
+	case PCM_C:
+		if (chip->model.device_config & CAPTURE_1_FROM_SPDIF) {
+			runtime->hw.rates &= ~(SNDRV_PCM_RATE_32000 |
+					       SNDRV_PCM_RATE_64000);
+			runtime->hw.rate_min = 44100;
+		}
+		/* fall through */
+	case PCM_A:
+	case PCM_B:
+		runtime->hw.fifo_size = 0;
+		break;
+	case PCM_MULTICH:
+		runtime->hw.channels_max = chip->model.dac_channels_pcm;
+		break;
+	}
+	if (chip->model.pcm_hardware_filter)
+		chip->model.pcm_hardware_filter(channel, &runtime->hw);
+	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->controls[CONTROL_SPDIF_PCM]->vd[0].access &=
+			~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE |
+			       SNDRV_CTL_EVENT_MASK_INFO,
+			       &chip->controls[CONTROL_SPDIF_PCM]->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 = oxygen_substream_channel(substream);
+
+	mutex_lock(&chip->mutex);
+	chip->pcm_active &= ~(1 << channel);
+	if (channel == PCM_SPDIF) {
+		chip->controls[CONTROL_SPDIF_PCM]->vd[0].access |=
+			SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE |
+			       SNDRV_CTL_EVENT_MASK_INFO,
+			       &chip->controls[CONTROL_SPDIF_PCM]->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_bits(struct snd_pcm_hw_params *hw_params)
+{
+	if (params_format(hw_params) == SNDRV_PCM_FORMAT_S32_LE)
+		return OXYGEN_I2S_BITS_24;
+	else
+		return OXYGEN_I2S_BITS_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 = oxygen_substream_channel(substream);
+	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 u16 get_mclk(struct oxygen *chip, unsigned int channel,
+		    struct snd_pcm_hw_params *params)
+{
+	unsigned int mclks, shift;
+
+	if (channel == PCM_MULTICH)
+		mclks = chip->model.dac_mclks;
+	else
+		mclks = chip->model.adc_mclks;
+
+	if (params_rate(params) <= 48000)
+		shift = 0;
+	else if (params_rate(params) <= 96000)
+		shift = 2;
+	else
+		shift = 4;
+
+	return OXYGEN_I2S_MCLK(mclks >> shift);
+}
+
+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_write16_masked(chip, OXYGEN_I2S_A_FORMAT,
+			      oxygen_rate(hw_params) |
+			      chip->model.adc_i2s_format |
+			      get_mclk(chip, PCM_A, hw_params) |
+			      oxygen_i2s_bits(hw_params),
+			      OXYGEN_I2S_RATE_MASK |
+			      OXYGEN_I2S_FORMAT_MASK |
+			      OXYGEN_I2S_MCLK_MASK |
+			      OXYGEN_I2S_BITS_MASK);
+	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 is_ac97;
+	int err;
+
+	err = oxygen_hw_params(substream, hw_params);
+	if (err < 0)
+		return err;
+
+	is_ac97 = chip->has_ac97_1 &&
+		(chip->model.device_config & CAPTURE_2_FROM_AC97_1);
+
+	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);
+	if (!is_ac97)
+		oxygen_write16_masked(chip, OXYGEN_I2S_B_FORMAT,
+				      oxygen_rate(hw_params) |
+				      chip->model.adc_i2s_format |
+				      get_mclk(chip, PCM_B, hw_params) |
+				      oxygen_i2s_bits(hw_params),
+				      OXYGEN_I2S_RATE_MASK |
+				      OXYGEN_I2S_FORMAT_MASK |
+				      OXYGEN_I2S_MCLK_MASK |
+				      OXYGEN_I2S_BITS_MASK);
+	spin_unlock_irq(&chip->reg_lock);
+
+	if (!is_ac97) {
+		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);
+	bool is_spdif;
+	int err;
+
+	err = oxygen_hw_params(substream, hw_params);
+	if (err < 0)
+		return err;
+
+	is_spdif = chip->model.device_config & CAPTURE_1_FROM_SPDIF;
+
+	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);
+	if (!is_spdif)
+		oxygen_write16_masked(chip, OXYGEN_I2S_C_FORMAT,
+				      oxygen_rate(hw_params) |
+				      chip->model.adc_i2s_format |
+				      get_mclk(chip, PCM_B, hw_params) |
+				      oxygen_i2s_bits(hw_params),
+				      OXYGEN_I2S_RATE_MASK |
+				      OXYGEN_I2S_FORMAT_MASK |
+				      OXYGEN_I2S_MCLK_MASK |
+				      OXYGEN_I2S_BITS_MASK);
+	spin_unlock_irq(&chip->reg_lock);
+
+	if (!is_spdif) {
+		mutex_lock(&chip->mutex);
+		chip->model.set_adc_params(chip, hw_params);
+		mutex_unlock(&chip->mutex);
+	}
+	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;
+
+	mutex_lock(&chip->mutex);
+	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);
+	mutex_unlock(&chip->mutex);
+	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;
+
+	mutex_lock(&chip->mutex);
+	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) |
+			      chip->model.dac_i2s_format |
+			      get_mclk(chip, PCM_MULTICH, hw_params) |
+			      oxygen_i2s_bits(hw_params),
+			      OXYGEN_I2S_RATE_MASK |
+			      OXYGEN_I2S_FORMAT_MASK |
+			      OXYGEN_I2S_MCLK_MASK |
+			      OXYGEN_I2S_BITS_MASK);
+	oxygen_update_spdif_source(chip);
+	spin_unlock_irq(&chip->reg_lock);
+
+	chip->model.set_dac_params(chip, hw_params);
+	oxygen_update_dac_routing(chip);
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int oxygen_hw_free(struct snd_pcm_substream *substream)
+{
+	struct oxygen *chip = snd_pcm_substream_chip(substream);
+	unsigned int channel = oxygen_substream_channel(substream);
+	unsigned int channel_mask = 1 << channel;
+
+	spin_lock_irq(&chip->reg_lock);
+	chip->interrupt_mask &= ~channel_mask;
+	oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask);
+
+	oxygen_set_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask);
+	oxygen_clear_bits8(chip, OXYGEN_DMA_FLUSH, channel_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 = oxygen_substream_channel(substream);
+	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);
+
+	if (substream->runtime->no_period_wakeup)
+		chip->interrupt_mask &= ~channel_mask;
+	else
+		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 pausing;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		pausing = 0;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		pausing = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_pcm_group_for_each_entry(s, substream) {
+		if (snd_pcm_substream_chip(s) == chip) {
+			mask |= 1 << oxygen_substream_channel(s);
+			snd_pcm_trigger_done(s, substream);
+		}
+	}
+
+	spin_lock(&chip->reg_lock);
+	if (!pausing) {
+		if (cmd == SNDRV_PCM_TRIGGER_START)
+			chip->pcm_running |= mask;
+		else
+			chip->pcm_running &= ~mask;
+		oxygen_write8(chip, OXYGEN_DMA_STATUS, chip->pcm_running);
+	} else {
+		if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH)
+			oxygen_set_bits8(chip, OXYGEN_DMA_PAUSE, mask);
+		else
+			oxygen_clear_bits8(chip, OXYGEN_DMA_PAUSE, mask);
+	}
+	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 = oxygen_substream_channel(substream);
+	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 const 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 const 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 const 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 const 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 const 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 const struct snd_pcm_ops oxygen_ac97_ops = {
+	.open      = oxygen_ac97_open,
+	.close     = oxygen_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.hw_params = oxygen_hw_params,
+	.hw_free   = oxygen_hw_free,
+	.prepare   = oxygen_prepare,
+	.trigger   = oxygen_trigger,
+	.pointer   = oxygen_pointer,
+};
+
+int oxygen_pcm_init(struct oxygen *chip)
+{
+	struct snd_pcm *pcm;
+	int outs, ins;
+	int err;
+
+	outs = !!(chip->model.device_config & PLAYBACK_0_TO_I2S);
+	ins = !!(chip->model.device_config & (CAPTURE_0_FROM_I2S_1 |
+					      CAPTURE_0_FROM_I2S_2));
+	if (outs | ins) {
+		err = snd_pcm_new(chip->card, "Multichannel",
+				  0, outs, ins, &pcm);
+		if (err < 0)
+			return err;
+		if (outs)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+					&oxygen_multich_ops);
+		if (chip->model.device_config & CAPTURE_0_FROM_I2S_1)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+					&oxygen_rec_a_ops);
+		else if (chip->model.device_config & CAPTURE_0_FROM_I2S_2)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+					&oxygen_rec_b_ops);
+		pcm->private_data = chip;
+		strcpy(pcm->name, "Multichannel");
+		if (outs)
+			snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
+						      SNDRV_DMA_TYPE_DEV,
+						      snd_dma_pci_data(chip->pci),
+						      DEFAULT_BUFFER_BYTES_MULTICH,
+						      BUFFER_BYTES_MAX_MULTICH);
+		if (ins)
+			snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+						      SNDRV_DMA_TYPE_DEV,
+						      snd_dma_pci_data(chip->pci),
+						      DEFAULT_BUFFER_BYTES,
+						      BUFFER_BYTES_MAX);
+	}
+
+	outs = !!(chip->model.device_config & PLAYBACK_1_TO_SPDIF);
+	ins = !!(chip->model.device_config & CAPTURE_1_FROM_SPDIF);
+	if (outs | ins) {
+		err = snd_pcm_new(chip->card, "Digital", 1, outs, ins, &pcm);
+		if (err < 0)
+			return err;
+		if (outs)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+					&oxygen_spdif_ops);
+		if (ins)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+					&oxygen_rec_c_ops);
+		pcm->private_data = chip;
+		strcpy(pcm->name, "Digital");
+		snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+						      snd_dma_pci_data(chip->pci),
+						      DEFAULT_BUFFER_BYTES,
+						      BUFFER_BYTES_MAX);
+	}
+
+	if (chip->has_ac97_1) {
+		outs = !!(chip->model.device_config & PLAYBACK_2_TO_AC97_1);
+		ins = !!(chip->model.device_config & CAPTURE_2_FROM_AC97_1);
+	} else {
+		outs = 0;
+		ins = !!(chip->model.device_config & CAPTURE_2_FROM_I2S_2);
+	}
+	if (outs | ins) {
+		err = snd_pcm_new(chip->card, outs ? "AC97" : "Analog2",
+				  2, outs, ins, &pcm);
+		if (err < 0)
+			return err;
+		if (outs) {
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+					&oxygen_ac97_ops);
+			oxygen_write8_masked(chip, OXYGEN_REC_ROUTING,
+					     OXYGEN_REC_B_ROUTE_AC97_1,
+					     OXYGEN_REC_B_ROUTE_MASK);
+		}
+		if (ins)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+					&oxygen_rec_b_ops);
+		pcm->private_data = chip;
+		strcpy(pcm->name, outs ? "Front Panel" : "Analog 2");
+		snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+						      snd_dma_pci_data(chip->pci),
+						      DEFAULT_BUFFER_BYTES,
+						      BUFFER_BYTES_MAX);
+	}
+
+	ins = !!(chip->model.device_config & CAPTURE_3_FROM_I2S_3);
+	if (ins) {
+		err = snd_pcm_new(chip->card, "Analog3", 3, 0, ins, &pcm);
+		if (err < 0)
+			return err;
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+				&oxygen_rec_c_ops);
+		oxygen_write8_masked(chip, OXYGEN_REC_ROUTING,
+				     OXYGEN_REC_C_ROUTE_I2S_ADC_3,
+				     OXYGEN_REC_C_ROUTE_MASK);
+		pcm->private_data = chip;
+		strcpy(pcm->name, "Analog 3");
+		snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+						      snd_dma_pci_data(chip->pci),
+						      DEFAULT_BUFFER_BYTES,
+						      BUFFER_BYTES_MAX);
+	}
+	return 0;
+}
diff --git a/sound/pci/oxygen/oxygen_regs.h b/sound/pci/oxygen/oxygen_regs.h
new file mode 100644
index 0000000..eca9d94
--- /dev/null
+++ b/sound/pci/oxygen/oxygen_regs.h
@@ -0,0 +1,459 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#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	/* 24 bits */
+#define OXYGEN_DMA_MULTICH_TCOUNT	0x28	/* 24 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_PAUSE		0x41	/* 1 = pause */
+/* OXYGEN_CHANNEL_* */
+
+#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_DMA_A_BURST_MASK	0x04
+#define  OXYGEN_DMA_A_BURST_8		0x00	/* dwords */
+#define  OXYGEN_DMA_A_BURST_16		0x04
+#define  OXYGEN_DMA_MULTICH_BURST_MASK	0x08
+#define  OXYGEN_DMA_MULTICH_BURST_8	0x00
+#define  OXYGEN_DMA_MULTICH_BURST_16	0x08
+
+#define OXYGEN_INTERRUPT_MASK		0x44
+/* OXYGEN_CHANNEL_* */
+#define  OXYGEN_INT_SPDIF_IN_DETECT	0x0100
+#define  OXYGEN_INT_MCU			0x0200
+#define  OXYGEN_INT_2WIRE		0x0400
+#define  OXYGEN_INT_GPIO		0x0800
+#define  OXYGEN_INT_MCB			0x2000
+#define  OXYGEN_INT_AC97		0x4000
+
+#define OXYGEN_INTERRUPT_STATUS		0x46
+/* OXYGEN_CHANNEL_* amd OXYGEN_INT_* */
+#define  OXYGEN_INT_MIDI		0x1000
+
+#define OXYGEN_MISC			0x48
+#define  OXYGEN_MISC_WRITE_PCI_SUBID	0x01
+#define  OXYGEN_MISC_LATENCY_3F		0x02
+#define  OXYGEN_MISC_REC_C_FROM_SPDIF	0x04
+#define  OXYGEN_MISC_REC_B_FROM_AC97	0x08
+#define  OXYGEN_MISC_REC_A_FROM_MULTICH	0x10
+#define  OXYGEN_MISC_PCI_MEM_W_1_CLOCK	0x20
+#define  OXYGEN_MISC_MIDI		0x40
+#define  OXYGEN_MISC_CRYSTAL_MASK	0x80
+#define  OXYGEN_MISC_CRYSTAL_24576	0x00
+#define  OXYGEN_MISC_CRYSTAL_27		0x80	/* MHz */
+
+#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
+/* OXYGEN_FORMAT_* */
+
+#define OXYGEN_REC_CHANNELS		0x4c
+#define  OXYGEN_REC_CHANNELS_MASK	0x07
+#define  OXYGEN_REC_CHANNELS_2_2_2	0x00	/* DMA A, B, C */
+#define  OXYGEN_REC_CHANNELS_4_2_2	0x01
+#define  OXYGEN_REC_CHANNELS_6_0_2	0x02
+#define  OXYGEN_REC_CHANNELS_6_2_0	0x03
+#define  OXYGEN_REC_CHANNELS_8_0_0	0x04
+
+#define OXYGEN_FUNCTION			0x50
+#define  OXYGEN_FUNCTION_CLOCK_MASK	0x01
+#define  OXYGEN_FUNCTION_CLOCK_PLL	0x00
+#define  OXYGEN_FUNCTION_CLOCK_CRYSTAL	0x01
+#define  OXYGEN_FUNCTION_RESET_CODEC	0x02
+#define  OXYGEN_FUNCTION_RESET_POL	0x04
+#define  OXYGEN_FUNCTION_PWDN		0x08
+#define  OXYGEN_FUNCTION_PWDN_EN	0x10
+#define  OXYGEN_FUNCTION_PWDN_POL	0x20
+#define  OXYGEN_FUNCTION_2WIRE_SPI_MASK	0x40
+#define  OXYGEN_FUNCTION_SPI		0x00
+#define  OXYGEN_FUNCTION_2WIRE		0x40
+#define  OXYGEN_FUNCTION_ENABLE_SPI_4_5	0x80	/* 0 = EEPROM */
+
+#define OXYGEN_I2S_MULTICH_FORMAT	0x60
+#define  OXYGEN_I2S_RATE_MASK		0x0007	/* LRCK */
+#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_FORMAT_MASK		0x0008
+#define  OXYGEN_I2S_FORMAT_I2S		0x0000
+#define  OXYGEN_I2S_FORMAT_LJUST	0x0008
+#define  OXYGEN_I2S_MCLK_MASK		0x0030	/* MCLK/LRCK */
+#define  OXYGEN_I2S_MCLK_SHIFT		4
+#define  MCLK_128			0
+#define  MCLK_256			1
+#define  MCLK_512			2
+#define  OXYGEN_I2S_MCLK(f)		(((f) & 3) << OXYGEN_I2S_MCLK_SHIFT)
+#define  OXYGEN_I2S_BITS_MASK		0x00c0
+#define  OXYGEN_I2S_BITS_16		0x0000
+#define  OXYGEN_I2S_BITS_20		0x0040
+#define  OXYGEN_I2S_BITS_24		0x0080
+#define  OXYGEN_I2S_BITS_32		0x00c0
+#define  OXYGEN_I2S_MASTER		0x0100
+#define  OXYGEN_I2S_BCLK_MASK		0x0600	/* BCLK/LRCK */
+#define  OXYGEN_I2S_BCLK_64		0x0000
+#define  OXYGEN_I2S_BCLK_128		0x0200
+#define  OXYGEN_I2S_BCLK_256		0x0400
+#define  OXYGEN_I2S_MUTE_MCLK		0x0800
+
+#define OXYGEN_I2S_A_FORMAT		0x62
+#define OXYGEN_I2S_B_FORMAT		0x64
+#define OXYGEN_I2S_C_FORMAT		0x66
+/* like OXYGEN_I2S_MULTICH_FORMAT */
+
+#define OXYGEN_SPDIF_CONTROL		0x70
+#define  OXYGEN_SPDIF_OUT_ENABLE	0x00000002
+#define  OXYGEN_SPDIF_LOOPBACK		0x00000004	/* in to out */
+#define  OXYGEN_SPDIF_SENSE_MASK	0x00000008
+#define  OXYGEN_SPDIF_LOCK_MASK		0x00000010
+#define  OXYGEN_SPDIF_RATE_MASK		0x00000020
+#define  OXYGEN_SPDIF_SPDVALID		0x00000040
+#define  OXYGEN_SPDIF_SENSE_PAR		0x00000200
+#define  OXYGEN_SPDIF_LOCK_PAR		0x00000400
+#define  OXYGEN_SPDIF_SENSE_STATUS	0x00000800
+#define  OXYGEN_SPDIF_LOCK_STATUS	0x00001000
+#define  OXYGEN_SPDIF_SENSE_INT		0x00002000	/* r/wc */
+#define  OXYGEN_SPDIF_LOCK_INT		0x00004000	/* r/wc */
+#define  OXYGEN_SPDIF_RATE_INT		0x00008000	/* r/wc */
+#define  OXYGEN_SPDIF_IN_CLOCK_MASK	0x00010000
+#define  OXYGEN_SPDIF_IN_CLOCK_96	0x00000000	/* <= 96 kHz */
+#define  OXYGEN_SPDIF_IN_CLOCK_192	0x00010000	/* > 96 kHz */
+#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_EEPROM_CONTROL		0x80
+#define  OXYGEN_EEPROM_ADDRESS_MASK	0x7f
+#define  OXYGEN_EEPROM_DIR_MASK		0x80
+#define  OXYGEN_EEPROM_DIR_READ		0x00
+#define  OXYGEN_EEPROM_DIR_WRITE	0x80
+
+#define OXYGEN_EEPROM_STATUS		0x81
+#define  OXYGEN_EEPROM_VALID		0x40
+#define  OXYGEN_EEPROM_BUSY		0x80
+
+#define OXYGEN_EEPROM_DATA		0x82	/* 16 bits */
+
+#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		0x0001
+#define  OXYGEN_2WIRE_LENGTH_MASK	0x0002
+#define  OXYGEN_2WIRE_LENGTH_8		0x0000
+#define  OXYGEN_2WIRE_LENGTH_16		0x0002
+#define  OXYGEN_2WIRE_MANUAL_READ	0x0004	/* 0 = auto read */
+#define  OXYGEN_2WIRE_WRITE_MAP_ONLY	0x0008
+#define  OXYGEN_2WIRE_SLAVE_AD_MASK	0x0030	/* AD0, AD1 */
+#define  OXYGEN_2WIRE_INTERRUPT_MASK	0x0040	/* 0 = int. if not responding */
+#define  OXYGEN_2WIRE_SLAVE_NO_RESPONSE	0x0080
+#define  OXYGEN_2WIRE_SPEED_MASK	0x0100
+#define  OXYGEN_2WIRE_SPEED_STANDARD	0x0000
+#define  OXYGEN_2WIRE_SPEED_FAST	0x0100
+#define  OXYGEN_2WIRE_CLOCK_SYNC	0x0200
+#define  OXYGEN_2WIRE_BUS_RESET		0x0400
+
+#define OXYGEN_SPI_CONTROL		0x98
+#define  OXYGEN_SPI_BUSY		0x01	/* read */
+#define  OXYGEN_SPI_TRIGGER		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_CLOCK_MASK		0x0c
+#define  OXYGEN_SPI_CLOCK_160		0x00	/* ns */
+#define  OXYGEN_SPI_CLOCK_320		0x04
+#define  OXYGEN_SPI_CLOCK_640		0x08
+#define  OXYGEN_SPI_CLOCK_1280		0x0c
+#define  OXYGEN_SPI_CODEC_MASK		0x70	/* 0..5 */
+#define  OXYGEN_SPI_CODEC_SHIFT		4
+#define  OXYGEN_SPI_CEN_MASK		0x80
+#define  OXYGEN_SPI_CEN_LATCH_CLOCK_LO	0x00
+#define  OXYGEN_SPI_CEN_LATCH_CLOCK_HI	0x80
+
+#define OXYGEN_SPI_DATA1		0x99
+#define OXYGEN_SPI_DATA2		0x9a
+#define OXYGEN_SPI_DATA3		0x9b
+
+#define OXYGEN_MPU401			0xa0
+
+#define OXYGEN_MPU401_CONTROL		0xa2
+#define  OXYGEN_MPU401_LOOPBACK		0x01	/* TXD to RXD */
+
+#define OXYGEN_GPI_DATA			0xa4
+/* bits 0..5 = pin XGPI0..XGPI5 */
+
+#define OXYGEN_GPI_INTERRUPT_MASK	0xa5
+/* bits 0..5, 1 = enable */
+
+#define OXYGEN_GPIO_DATA		0xa6
+/* bits 0..9 */
+
+#define OXYGEN_GPIO_CONTROL		0xa8
+/* bits 0..9, 0 = input, 1 = output */
+#define  OXYGEN_GPIO1_XSLAVE_RDY	0x8000
+
+#define OXYGEN_GPIO_INTERRUPT_MASK	0xaa
+/* bits 0..9, 1 = enable */
+
+#define OXYGEN_DEVICE_SENSE		0xac
+#define  OXYGEN_HEAD_PHONE_DETECT	0x01
+#define  OXYGEN_HEAD_PHONE_MASK		0x06
+#define  OXYGEN_HEAD_PHONE_PASSIVE_SPK	0x00
+#define  OXYGEN_HEAD_PHONE_HP		0x02
+#define  OXYGEN_HEAD_PHONE_ACTIVE_SPK	0x04
+
+#define OXYGEN_MCU_2WIRE_DATA		0xb0
+
+#define OXYGEN_MCU_2WIRE_MAP		0xb2
+
+#define OXYGEN_MCU_2WIRE_STATUS		0xb3
+#define  OXYGEN_MCU_2WIRE_BUSY		0x01
+#define  OXYGEN_MCU_2WIRE_LENGTH_MASK	0x06
+#define  OXYGEN_MCU_2WIRE_LENGTH_1	0x00
+#define  OXYGEN_MCU_2WIRE_LENGTH_2	0x02
+#define  OXYGEN_MCU_2WIRE_LENGTH_3	0x04
+#define  OXYGEN_MCU_2WIRE_WRITE		0x08	/* r/wc */
+#define  OXYGEN_MCU_2WIRE_READ		0x10	/* r/wc */
+#define  OXYGEN_MCU_2WIRE_DRV_XACT_FAIL	0x20	/* r/wc */
+#define  OXYGEN_MCU_2WIRE_RESET		0x40
+
+#define OXYGEN_MCU_2WIRE_CONTROL	0xb4
+#define  OXYGEN_MCU_2WIRE_DRV_ACK	0x01
+#define  OXYGEN_MCU_2WIRE_DRV_XACT	0x02
+#define  OXYGEN_MCU_2WIRE_INT_MASK	0x04
+#define  OXYGEN_MCU_2WIRE_SYNC_MASK	0x08
+#define  OXYGEN_MCU_2WIRE_SYNC_RDY_PIN	0x00
+#define  OXYGEN_MCU_2WIRE_SYNC_DATA	0x08
+#define  OXYGEN_MCU_2WIRE_ADDRESS_MASK	0x30
+#define  OXYGEN_MCU_2WIRE_ADDRESS_10	0x00
+#define  OXYGEN_MCU_2WIRE_ADDRESS_12	0x10
+#define  OXYGEN_MCU_2WIRE_ADDRESS_14	0x20
+#define  OXYGEN_MCU_2WIRE_ADDRESS_16	0x30
+#define  OXYGEN_MCU_2WIRE_INT_POL	0x40
+#define  OXYGEN_MCU_2WIRE_SYNC_ENABLE	0x80
+
+#define OXYGEN_PLAY_ROUTING		0xc0
+#define  OXYGEN_PLAY_MUTE01		0x0001
+#define  OXYGEN_PLAY_MUTE23		0x0002
+#define  OXYGEN_PLAY_MUTE45		0x0004
+#define  OXYGEN_PLAY_MUTE67		0x0008
+#define  OXYGEN_PLAY_MUTE_MASK		0x000f
+#define  OXYGEN_PLAY_MULTICH_MASK	0x0010
+#define  OXYGEN_PLAY_MULTICH_I2S_DAC	0x0000
+#define  OXYGEN_PLAY_MULTICH_AC97	0x0010
+#define  OXYGEN_PLAY_SPDIF_MASK		0x00e0
+#define  OXYGEN_PLAY_SPDIF_SPDIF	0x0000
+#define  OXYGEN_PLAY_SPDIF_MULTICH_01	0x0020
+#define  OXYGEN_PLAY_SPDIF_MULTICH_23	0x0040
+#define  OXYGEN_PLAY_SPDIF_MULTICH_45	0x0060
+#define  OXYGEN_PLAY_SPDIF_MULTICH_67	0x0080
+#define  OXYGEN_PLAY_SPDIF_REC_A	0x00a0
+#define  OXYGEN_PLAY_SPDIF_REC_B	0x00c0
+#define  OXYGEN_PLAY_SPDIF_I2S_ADC_3	0x00e0
+#define  OXYGEN_PLAY_DAC0_SOURCE_MASK	0x0300
+#define  OXYGEN_PLAY_DAC0_SOURCE_SHIFT	8
+#define  OXYGEN_PLAY_DAC1_SOURCE_MASK	0x0c00
+#define  OXYGEN_PLAY_DAC1_SOURCE_SHIFT	10
+#define  OXYGEN_PLAY_DAC2_SOURCE_MASK	0x3000
+#define  OXYGEN_PLAY_DAC2_SOURCE_SHIFT	12
+#define  OXYGEN_PLAY_DAC3_SOURCE_MASK	0xc000
+#define  OXYGEN_PLAY_DAC3_SOURCE_SHIFT	14
+
+#define OXYGEN_REC_ROUTING		0xc2
+#define  OXYGEN_MUTE_I2S_ADC_1		0x01
+#define  OXYGEN_MUTE_I2S_ADC_2		0x02
+#define  OXYGEN_MUTE_I2S_ADC_3		0x04
+#define  OXYGEN_REC_A_ROUTE_MASK	0x08
+#define  OXYGEN_REC_A_ROUTE_I2S_ADC_1	0x00
+#define  OXYGEN_REC_A_ROUTE_AC97_0	0x08
+#define  OXYGEN_REC_B_ROUTE_MASK	0x10
+#define  OXYGEN_REC_B_ROUTE_I2S_ADC_2	0x00
+#define  OXYGEN_REC_B_ROUTE_AC97_1	0x10
+#define  OXYGEN_REC_C_ROUTE_MASK	0x20
+#define  OXYGEN_REC_C_ROUTE_SPDIF	0x00
+#define  OXYGEN_REC_C_ROUTE_I2S_ADC_3	0x20
+
+#define OXYGEN_ADC_MONITOR		0xc3
+#define  OXYGEN_ADC_MONITOR_A		0x01
+#define  OXYGEN_ADC_MONITOR_A_HALF_VOL	0x02
+#define  OXYGEN_ADC_MONITOR_B		0x04
+#define  OXYGEN_ADC_MONITOR_B_HALF_VOL	0x08
+#define  OXYGEN_ADC_MONITOR_C		0x10
+#define  OXYGEN_ADC_MONITOR_C_HALF_VOL	0x20
+
+#define OXYGEN_A_MONITOR_ROUTING	0xc4
+#define  OXYGEN_A_MONITOR_ROUTE_0_MASK	0x03
+#define  OXYGEN_A_MONITOR_ROUTE_0_SHIFT	0
+#define  OXYGEN_A_MONITOR_ROUTE_1_MASK	0x0c
+#define  OXYGEN_A_MONITOR_ROUTE_1_SHIFT	2
+#define  OXYGEN_A_MONITOR_ROUTE_2_MASK	0x30
+#define  OXYGEN_A_MONITOR_ROUTE_2_SHIFT	4
+#define  OXYGEN_A_MONITOR_ROUTE_3_MASK	0xc0
+#define  OXYGEN_A_MONITOR_ROUTE_3_SHIFT	6
+
+#define OXYGEN_AC97_CONTROL		0xd0
+#define  OXYGEN_AC97_COLD_RESET		0x0001
+#define  OXYGEN_AC97_SUSPENDED		0x0002	/* read */
+#define  OXYGEN_AC97_RESUME		0x0002	/* write */
+#define  OXYGEN_AC97_CLOCK_DISABLE	0x0004
+#define  OXYGEN_AC97_NO_CODEC_0		0x0008
+#define  OXYGEN_AC97_CODEC_0		0x0010
+#define  OXYGEN_AC97_CODEC_1		0x0020
+
+#define OXYGEN_AC97_INTERRUPT_MASK	0xd2
+#define  OXYGEN_AC97_INT_READ_DONE	0x01
+#define  OXYGEN_AC97_INT_WRITE_DONE	0x02
+#define  OXYGEN_AC97_INT_CODEC_0	0x10
+#define  OXYGEN_AC97_INT_CODEC_1	0x20
+
+#define OXYGEN_AC97_INTERRUPT_STATUS	0xd3
+/* OXYGEN_AC97_INT_* */
+
+#define OXYGEN_AC97_OUT_CONFIG		0xd4
+#define  OXYGEN_AC97_CODEC1_SLOT3	0x00000001
+#define  OXYGEN_AC97_CODEC1_SLOT3_VSR	0x00000002
+#define  OXYGEN_AC97_CODEC1_SLOT4	0x00000010
+#define  OXYGEN_AC97_CODEC1_SLOT4_VSR	0x00000020
+#define  OXYGEN_AC97_CODEC0_FRONTL	0x00000100
+#define  OXYGEN_AC97_CODEC0_FRONTR	0x00000200
+#define  OXYGEN_AC97_CODEC0_SIDEL	0x00000400
+#define  OXYGEN_AC97_CODEC0_SIDER	0x00000800
+#define  OXYGEN_AC97_CODEC0_CENTER	0x00001000
+#define  OXYGEN_AC97_CODEC0_BASE	0x00002000
+#define  OXYGEN_AC97_CODEC0_REARL	0x00004000
+#define  OXYGEN_AC97_CODEC0_REARR	0x00008000
+
+#define OXYGEN_AC97_IN_CONFIG		0xd8
+#define  OXYGEN_AC97_CODEC1_LINEL	0x00000001
+#define  OXYGEN_AC97_CODEC1_LINEL_VSR	0x00000002
+#define  OXYGEN_AC97_CODEC1_LINEL_16	0x00000000
+#define  OXYGEN_AC97_CODEC1_LINEL_18	0x00000004
+#define  OXYGEN_AC97_CODEC1_LINEL_20	0x00000008
+#define  OXYGEN_AC97_CODEC1_LINER	0x00000010
+#define  OXYGEN_AC97_CODEC1_LINER_VSR	0x00000020
+#define  OXYGEN_AC97_CODEC1_LINER_16	0x00000000
+#define  OXYGEN_AC97_CODEC1_LINER_18	0x00000040
+#define  OXYGEN_AC97_CODEC1_LINER_20	0x00000080
+#define  OXYGEN_AC97_CODEC0_LINEL	0x00000100
+#define  OXYGEN_AC97_CODEC0_LINER	0x00000200
+
+#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_TEST			0xe0
+#define  OXYGEN_TEST_RAM_SUCCEEDED	0x01
+#define  OXYGEN_TEST_PLAYBACK_RAM	0x02
+#define  OXYGEN_TEST_RECORD_RAM		0x04
+#define  OXYGEN_TEST_PLL		0x08
+#define  OXYGEN_TEST_2WIRE_LOOPBACK	0x10
+
+#define OXYGEN_DMA_FLUSH		0xe1
+/* OXYGEN_CHANNEL_* */
+
+#define OXYGEN_CODEC_VERSION		0xe4
+#define  OXYGEN_CODEC_ID_MASK		0x07
+
+#define OXYGEN_REVISION			0xe6
+#define  OXYGEN_PACKAGE_ID_MASK		0x0007
+#define  OXYGEN_PACKAGE_ID_8786		0x0004
+#define  OXYGEN_PACKAGE_ID_8787		0x0006
+#define  OXYGEN_PACKAGE_ID_8788		0x0007
+#define  OXYGEN_REVISION_MASK		0xfff8
+#define  OXYGEN_REVISION_2		0x0008
+
+#define OXYGEN_OFFSIN_48K		0xe8
+#define OXYGEN_OFFSBASE_48K		0xe9
+#define  OXYGEN_OFFSBASE_MASK		0x0fff
+#define OXYGEN_OFFSIN_44K		0xec
+#define OXYGEN_OFFSBASE_44K		0xed
+
+#endif
diff --git a/sound/pci/oxygen/pcm1796.h b/sound/pci/oxygen/pcm1796.h
new file mode 100644
index 0000000..34d07dd
--- /dev/null
+++ b/sound/pci/oxygen/pcm1796.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef PCM1796_H_INCLUDED
+#define PCM1796_H_INCLUDED
+
+/* register 16 */
+#define PCM1796_ATL_MASK	0xff
+/* register 17 */
+#define PCM1796_ATR_MASK	0xff
+/* register 18 */
+#define PCM1796_MUTE		0x01
+#define PCM1796_DME		0x02
+#define PCM1796_DMF_MASK	0x0c
+#define PCM1796_DMF_DISABLED	0x00
+#define PCM1796_DMF_48		0x04
+#define PCM1796_DMF_441		0x08
+#define PCM1796_DMF_32		0x0c
+#define PCM1796_FMT_MASK	0x70
+#define PCM1796_FMT_16_RJUST	0x00
+#define PCM1796_FMT_20_RJUST	0x10
+#define PCM1796_FMT_24_RJUST	0x20
+#define PCM1796_FMT_24_LJUST	0x30
+#define PCM1796_FMT_16_I2S	0x40
+#define PCM1796_FMT_24_I2S	0x50
+#define PCM1796_ATLD		0x80
+/* register 19 */
+#define PCM1796_INZD		0x01
+#define PCM1796_FLT_MASK	0x02
+#define PCM1796_FLT_SHARP	0x00
+#define PCM1796_FLT_SLOW	0x02
+#define PCM1796_DFMS		0x04
+#define PCM1796_OPE		0x10
+#define PCM1796_ATS_MASK	0x60
+#define PCM1796_ATS_1		0x00
+#define PCM1796_ATS_2		0x20
+#define PCM1796_ATS_4		0x40
+#define PCM1796_ATS_8		0x60
+#define PCM1796_REV		0x80
+/* register 20 */
+#define PCM1796_OS_MASK		0x03
+#define PCM1796_OS_64		0x00
+#define PCM1796_OS_32		0x01
+#define PCM1796_OS_128		0x02
+#define PCM1796_CHSL_MASK	0x04
+#define PCM1796_CHSL_LEFT	0x00
+#define PCM1796_CHSL_RIGHT	0x04
+#define PCM1796_MONO		0x08
+#define PCM1796_DFTH		0x10
+#define PCM1796_DSD		0x20
+#define PCM1796_SRST		0x40
+/* register 21 */
+#define PCM1796_PCMZ		0x01
+#define PCM1796_DZ_MASK		0x06
+/* register 22 */
+#define PCM1796_ZFGL		0x01
+#define PCM1796_ZFGR		0x02
+/* register 23 */
+#define PCM1796_ID_MASK		0x1f
+
+#endif
diff --git a/sound/pci/oxygen/se6x.c b/sound/pci/oxygen/se6x.c
new file mode 100644
index 0000000..f70d514
--- /dev/null
+++ b/sound/pci/oxygen/se6x.c
@@ -0,0 +1,160 @@
+/*
+ * C-Media CMI8787 driver for the Studio Evolution SE6X
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * CMI8787:
+ *
+ *   SPI    -> microcontroller (not actually used)
+ *   GPIO 0 -> do.
+ *   GPIO 2 -> do.
+ *
+ *   DAC0   -> both PCM1792A (L+R, each in mono mode)
+ *   ADC1  <-  1st PCM1804
+ *   ADC2  <-  2nd PCM1804
+ *   ADC3  <-  3rd PCM1804
+ */
+
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include "oxygen.h"
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("Studio Evolution SE6X driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{Studio Evolution,SE6X}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool 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 const struct pci_device_id se6x_ids[] = {
+	{ OXYGEN_PCI_SUBID(0x13f6, 0x8788) },
+	{ }
+};
+MODULE_DEVICE_TABLE(pci, se6x_ids);
+
+static void se6x_init(struct oxygen *chip)
+{
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, 0x005);
+
+	snd_component_add(chip->card, "PCM1792A");
+	snd_component_add(chip->card, "PCM1804");
+}
+
+static int se6x_control_filter(struct snd_kcontrol_new *template)
+{
+	/* no DAC volume/mute */
+	if (!strncmp(template->name, "Master Playback ", 16))
+		return 1;
+	return 0;
+}
+
+static void se6x_cleanup(struct oxygen *chip)
+{
+}
+
+static void set_pcm1792a_params(struct oxygen *chip,
+				struct snd_pcm_hw_params *params)
+{
+	/* nothing to do (the microcontroller monitors DAC_LRCK) */
+}
+
+static void set_pcm1804_params(struct oxygen *chip,
+			       struct snd_pcm_hw_params *params)
+{
+}
+
+static unsigned int se6x_adjust_dac_routing(struct oxygen *chip,
+					    unsigned int play_routing)
+{
+	/* route the same stereo pair to DAC0 and DAC1 */
+	return ( play_routing       & OXYGEN_PLAY_DAC0_SOURCE_MASK) |
+	       ((play_routing << 2) & OXYGEN_PLAY_DAC1_SOURCE_MASK);
+}
+
+static const struct oxygen_model model_se6x = {
+	.shortname = "Studio Evolution SE6X",
+	.longname = "C-Media Oxygen HD Audio",
+	.chip = "CMI8787",
+	.init = se6x_init,
+	.control_filter = se6x_control_filter,
+	.cleanup = se6x_cleanup,
+	.set_dac_params = set_pcm1792a_params,
+	.set_adc_params = set_pcm1804_params,
+	.adjust_dac_routing = se6x_adjust_dac_routing,
+	.device_config = PLAYBACK_0_TO_I2S |
+			 CAPTURE_0_FROM_I2S_1 |
+			 CAPTURE_2_FROM_I2S_2 |
+			 CAPTURE_3_FROM_I2S_3,
+	.dac_channels_pcm = 2,
+	.function_flags = OXYGEN_FUNCTION_SPI,
+	.dac_mclks = OXYGEN_MCLKS(256, 128, 128),
+	.adc_mclks = OXYGEN_MCLKS(256, 256, 128),
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_I2S,
+};
+
+static int se6x_get_model(struct oxygen *chip,
+			  const struct pci_device_id *pci_id)
+{
+	chip->model = model_se6x;
+	return 0;
+}
+
+static int se6x_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+{
+	static int dev;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		++dev;
+		return -ENOENT;
+	}
+	err = oxygen_pci_probe(pci, index[dev], id[dev], THIS_MODULE,
+			       se6x_ids, se6x_get_model);
+	if (err >= 0)
+		++dev;
+	return err;
+}
+
+static struct pci_driver se6x_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = se6x_ids,
+	.probe = se6x_probe,
+	.remove = oxygen_pci_remove,
+#ifdef CONFIG_PM_SLEEP
+	.driver = {
+		.pm = &oxygen_pci_pm,
+	},
+#endif
+	.shutdown = oxygen_pci_shutdown,
+};
+
+module_pci_driver(se6x_driver);
diff --git a/sound/pci/oxygen/virtuoso.c b/sound/pci/oxygen/virtuoso.c
new file mode 100644
index 0000000..83de6fb
--- /dev/null
+++ b/sound/pci/oxygen/virtuoso.c
@@ -0,0 +1,107 @@
+/*
+ * C-Media CMI8788 driver for Asus Xonar cards
+ *
+ * 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 <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include "xonar.h"
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("Asus Virtuoso driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{Asus,AV66},{Asus,AV100},{Asus,AV200}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool 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 const struct pci_device_id xonar_ids[] = {
+	{ OXYGEN_PCI_SUBID(0x1043, 0x8269) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x8275) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x82b7) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x8314) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x8327) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x834f) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x835c) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x835d) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x835e) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x838e) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x8428) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x8522) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x85f4) },
+	{ OXYGEN_PCI_SUBID_BROKEN_EEPROM },
+	{ }
+};
+MODULE_DEVICE_TABLE(pci, xonar_ids);
+
+static int get_xonar_model(struct oxygen *chip,
+			   const struct pci_device_id *id)
+{
+	if (get_xonar_pcm179x_model(chip, id) >= 0)
+		return 0;
+	if (get_xonar_cs43xx_model(chip, id) >= 0)
+		return 0;
+	if (get_xonar_wm87x6_model(chip, id) >= 0)
+		return 0;
+	return -EINVAL;
+}
+
+static int xonar_probe(struct pci_dev *pci,
+		       const struct pci_device_id *pci_id)
+{
+	static int dev;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		++dev;
+		return -ENOENT;
+	}
+	err = oxygen_pci_probe(pci, index[dev], id[dev], THIS_MODULE,
+			       xonar_ids, get_xonar_model);
+	if (err >= 0)
+		++dev;
+	return err;
+}
+
+static struct pci_driver xonar_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = xonar_ids,
+	.probe = xonar_probe,
+	.remove = oxygen_pci_remove,
+#ifdef CONFIG_PM_SLEEP
+	.driver = {
+		.pm = &oxygen_pci_pm,
+	},
+#endif
+	.shutdown = oxygen_pci_shutdown,
+};
+
+module_pci_driver(xonar_driver);
diff --git a/sound/pci/oxygen/wm8766.h b/sound/pci/oxygen/wm8766.h
new file mode 100644
index 0000000..be83ad4
--- /dev/null
+++ b/sound/pci/oxygen/wm8766.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef WM8766_H_INCLUDED
+#define WM8766_H_INCLUDED
+
+#define WM8766_LDA1		0x00
+#define WM8766_RDA1		0x01
+#define WM8766_DAC_CTRL		0x02
+#define WM8766_INT_CTRL		0x03
+#define WM8766_LDA2		0x04
+#define WM8766_RDA2		0x05
+#define WM8766_LDA3		0x06
+#define WM8766_RDA3		0x07
+#define WM8766_MASTDA		0x08
+#define WM8766_DAC_CTRL2	0x09
+#define WM8766_DAC_CTRL3	0x0a
+#define WM8766_MUTE1		0x0c
+#define WM8766_MUTE2		0x0f
+#define WM8766_RESET		0x1f
+
+/* LDAx/RDAx/MASTDA */
+#define WM8766_ATT_MASK		0x0ff
+#define WM8766_UPDATE		0x100
+/* DAC_CTRL */
+#define WM8766_MUTEALL		0x001
+#define WM8766_DEEMPALL		0x002
+#define WM8766_PWDN		0x004
+#define WM8766_ATC		0x008
+#define WM8766_IZD		0x010
+#define WM8766_PL_LEFT_MASK	0x060
+#define WM8766_PL_LEFT_MUTE	0x000
+#define WM8766_PL_LEFT_LEFT	0x020
+#define WM8766_PL_LEFT_RIGHT	0x040
+#define WM8766_PL_LEFT_LRMIX	0x060
+#define WM8766_PL_RIGHT_MASK	0x180
+#define WM8766_PL_RIGHT_MUTE	0x000
+#define WM8766_PL_RIGHT_LEFT	0x080
+#define WM8766_PL_RIGHT_RIGHT	0x100
+#define WM8766_PL_RIGHT_LRMIX	0x180
+/* INT_CTRL */
+#define WM8766_FMT_MASK		0x003
+#define WM8766_FMT_RJUST	0x000
+#define WM8766_FMT_LJUST	0x001
+#define WM8766_FMT_I2S		0x002
+#define WM8766_FMT_DSP		0x003
+#define WM8766_LRP		0x004
+#define WM8766_BCP		0x008
+#define WM8766_IWL_MASK		0x030
+#define WM8766_IWL_16		0x000
+#define WM8766_IWL_20		0x010
+#define WM8766_IWL_24		0x020
+#define WM8766_IWL_32		0x030
+#define WM8766_PHASE_MASK	0x1c0
+/* DAC_CTRL2 */
+#define WM8766_ZCD		0x001
+#define WM8766_DZFM_MASK	0x006
+#define WM8766_DMUTE_MASK	0x038
+#define WM8766_DEEMP_MASK	0x1c0
+/* DAC_CTRL3 */
+#define WM8766_DACPD_MASK	0x00e
+#define WM8766_PWRDNALL		0x010
+#define WM8766_MS		0x020
+#define WM8766_RATE_MASK	0x1c0
+#define WM8766_RATE_128		0x000
+#define WM8766_RATE_192		0x040
+#define WM8766_RATE_256		0x080
+#define WM8766_RATE_384		0x0c0
+#define WM8766_RATE_512		0x100
+#define WM8766_RATE_768		0x140
+/* MUTE1 */
+#define WM8766_MPD1		0x040
+/* MUTE2 */
+#define WM8766_MPD2		0x020
+
+#endif
diff --git a/sound/pci/oxygen/wm8776.h b/sound/pci/oxygen/wm8776.h
new file mode 100644
index 0000000..1a96f56
--- /dev/null
+++ b/sound/pci/oxygen/wm8776.h
@@ -0,0 +1,177 @@
+#ifndef WM8776_H_INCLUDED
+#define WM8776_H_INCLUDED
+
+/*
+ * the following register names are from:
+ * wm8776.h  --  WM8776 ASoC driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define WM8776_HPLVOL		0x00
+#define WM8776_HPRVOL		0x01
+#define WM8776_HPMASTER		0x02
+#define WM8776_DACLVOL		0x03
+#define WM8776_DACRVOL		0x04
+#define WM8776_DACMASTER	0x05
+#define WM8776_PHASESWAP	0x06
+#define WM8776_DACCTRL1		0x07
+#define WM8776_DACMUTE		0x08
+#define WM8776_DACCTRL2		0x09
+#define WM8776_DACIFCTRL	0x0a
+#define WM8776_ADCIFCTRL	0x0b
+#define WM8776_MSTRCTRL		0x0c
+#define WM8776_PWRDOWN		0x0d
+#define WM8776_ADCLVOL		0x0e
+#define WM8776_ADCRVOL		0x0f
+#define WM8776_ALCCTRL1		0x10
+#define WM8776_ALCCTRL2		0x11
+#define WM8776_ALCCTRL3		0x12
+#define WM8776_NOISEGATE	0x13
+#define WM8776_LIMITER		0x14
+#define WM8776_ADCMUX		0x15
+#define WM8776_OUTMUX		0x16
+#define WM8776_RESET		0x17
+
+
+/* HPLVOL/HPRVOL/HPMASTER */
+#define WM8776_HPATT_MASK	0x07f
+#define WM8776_HPZCEN		0x080
+#define WM8776_UPDATE		0x100
+
+/* DACLVOL/DACRVOL/DACMASTER */
+#define WM8776_DATT_MASK	0x0ff
+/*#define WM8776_UPDATE		0x100*/
+
+/* PHASESWAP */
+#define WM8776_PH_MASK		0x003
+
+/* DACCTRL1 */
+#define WM8776_DZCEN		0x001
+#define WM8776_ATC		0x002
+#define WM8776_IZD		0x004
+#define WM8776_TOD		0x008
+#define WM8776_PL_LEFT_MASK	0x030
+#define WM8776_PL_LEFT_MUTE	0x000
+#define WM8776_PL_LEFT_LEFT	0x010
+#define WM8776_PL_LEFT_RIGHT	0x020
+#define WM8776_PL_LEFT_LRMIX	0x030
+#define WM8776_PL_RIGHT_MASK	0x0c0
+#define WM8776_PL_RIGHT_MUTE	0x000
+#define WM8776_PL_RIGHT_LEFT	0x040
+#define WM8776_PL_RIGHT_RIGHT	0x080
+#define WM8776_PL_RIGHT_LRMIX	0x0c0
+
+/* DACMUTE */
+#define WM8776_DMUTE		0x001
+
+/* DACCTRL2 */
+#define WM8776_DEEMPH		0x001
+#define WM8776_DZFM_MASK	0x006
+#define WM8776_DZFM_NONE	0x000
+#define WM8776_DZFM_LR		0x002
+#define WM8776_DZFM_BOTH	0x004
+#define WM8776_DZFM_EITHER	0x006
+
+/* DACIFCTRL */
+#define WM8776_DACFMT_MASK	0x003
+#define WM8776_DACFMT_RJUST	0x000
+#define WM8776_DACFMT_LJUST	0x001
+#define WM8776_DACFMT_I2S	0x002
+#define WM8776_DACFMT_DSP	0x003
+#define WM8776_DACLRP		0x004
+#define WM8776_DACBCP		0x008
+#define WM8776_DACWL_MASK	0x030
+#define WM8776_DACWL_16		0x000
+#define WM8776_DACWL_20		0x010
+#define WM8776_DACWL_24		0x020
+#define WM8776_DACWL_32		0x030
+
+/* ADCIFCTRL */
+#define WM8776_ADCFMT_MASK	0x003
+#define WM8776_ADCFMT_RJUST	0x000
+#define WM8776_ADCFMT_LJUST	0x001
+#define WM8776_ADCFMT_I2S	0x002
+#define WM8776_ADCFMT_DSP	0x003
+#define WM8776_ADCLRP		0x004
+#define WM8776_ADCBCP		0x008
+#define WM8776_ADCWL_MASK	0x030
+#define WM8776_ADCWL_16		0x000
+#define WM8776_ADCWL_20		0x010
+#define WM8776_ADCWL_24		0x020
+#define WM8776_ADCWL_32		0x030
+#define WM8776_ADCMCLK		0x040
+#define WM8776_ADCHPD		0x100
+
+/* MSTRCTRL */
+#define WM8776_ADCRATE_MASK	0x007
+#define WM8776_ADCRATE_256	0x002
+#define WM8776_ADCRATE_384	0x003
+#define WM8776_ADCRATE_512	0x004
+#define WM8776_ADCRATE_768	0x005
+#define WM8776_ADCOSR		0x008
+#define WM8776_DACRATE_MASK	0x070
+#define WM8776_DACRATE_128	0x000
+#define WM8776_DACRATE_192	0x010
+#define WM8776_DACRATE_256	0x020
+#define WM8776_DACRATE_384	0x030
+#define WM8776_DACRATE_512	0x040
+#define WM8776_DACRATE_768	0x050
+#define WM8776_DACMS		0x080
+#define WM8776_ADCMS		0x100
+
+/* PWRDOWN */
+#define WM8776_PDWN		0x001
+#define WM8776_ADCPD		0x002
+#define WM8776_DACPD		0x004
+#define WM8776_HPPD		0x008
+#define WM8776_AINPD		0x040
+
+/* ADCLVOL/ADCRVOL */
+#define WM8776_AGMASK		0x0ff
+#define WM8776_ZCA		0x100
+
+/* ALCCTRL1 */
+#define WM8776_LCT_MASK		0x00f
+#define WM8776_MAXGAIN_MASK	0x070
+#define WM8776_LCSEL_MASK	0x180
+#define WM8776_LCSEL_LIMITER	0x000
+#define WM8776_LCSEL_ALC_RIGHT 0x080
+#define WM8776_LCSEL_ALC_LEFT	0x100
+#define WM8776_LCSEL_ALC_STEREO	0x180
+
+/* ALCCTRL2 */
+#define WM8776_HLD_MASK		0x00f
+#define WM8776_ALCZC		0x080
+#define WM8776_LCEN		0x100
+
+/* ALCCTRL3 */
+#define WM8776_ATK_MASK		0x00f
+#define WM8776_DCY_MASK		0x0f0
+
+/* NOISEGATE */
+#define WM8776_NGAT		0x001
+#define WM8776_NGTH_MASK	0x01c
+
+/* LIMITER */
+#define WM8776_MAXATTEN_MASK	0x00f
+#define WM8776_TRANWIN_MASK	0x070
+
+/* ADCMUX */
+#define WM8776_AMX_MASK		0x01f
+#define WM8776_MUTERA		0x040
+#define WM8776_MUTELA		0x080
+#define WM8776_LRBOTH		0x100
+
+/* OUTMUX */
+#define WM8776_MX_DAC		0x001
+#define WM8776_MX_AUX		0x002
+#define WM8776_MX_BYPASS	0x004
+
+#endif
diff --git a/sound/pci/oxygen/wm8785.h b/sound/pci/oxygen/wm8785.h
new file mode 100644
index 0000000..21b9325
--- /dev/null
+++ b/sound/pci/oxygen/wm8785.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef WM8785_H_INCLUDED
+#define WM8785_H_INCLUDED
+
+#define WM8785_R0	0
+#define WM8785_R1	1
+#define WM8785_R2	2
+#define WM8785_R7	7
+
+/* R0 */
+#define WM8785_MCR_MASK		0x007
+#define WM8785_MCR_SLAVE	0x000
+#define WM8785_MCR_MASTER_128	0x001
+#define WM8785_MCR_MASTER_192	0x002
+#define WM8785_MCR_MASTER_256	0x003
+#define WM8785_MCR_MASTER_384	0x004
+#define WM8785_MCR_MASTER_512	0x005
+#define WM8785_MCR_MASTER_768	0x006
+#define WM8785_OSR_MASK		0x018
+#define WM8785_OSR_SINGLE	0x000
+#define WM8785_OSR_DOUBLE	0x008
+#define WM8785_OSR_QUAD		0x010
+#define WM8785_FORMAT_MASK	0x060
+#define WM8785_FORMAT_RJUST	0x000
+#define WM8785_FORMAT_LJUST	0x020
+#define WM8785_FORMAT_I2S	0x040
+#define WM8785_FORMAT_DSP	0x060
+/* R1 */
+#define WM8785_WL_MASK		0x003
+#define WM8785_WL_16		0x000
+#define WM8785_WL_20		0x001
+#define WM8785_WL_24		0x002
+#define WM8785_WL_32		0x003
+#define WM8785_LRP		0x004
+#define WM8785_BCLKINV		0x008
+#define WM8785_LRSWAP		0x010
+#define WM8785_DEVNO_MASK	0x0e0
+/* R2 */
+#define WM8785_HPFR		0x001
+#define WM8785_HPFL		0x002
+#define WM8785_SDODIS		0x004
+#define WM8785_PWRDNR		0x008
+#define WM8785_PWRDNL		0x010
+#define WM8785_TDM_MASK		0x1c0
+
+#endif
diff --git a/sound/pci/oxygen/xonar.h b/sound/pci/oxygen/xonar.h
new file mode 100644
index 0000000..3e37388
--- /dev/null
+++ b/sound/pci/oxygen/xonar.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef XONAR_H_INCLUDED
+#define XONAR_H_INCLUDED
+
+#include "oxygen.h"
+
+struct xonar_generic {
+	unsigned int anti_pop_delay;
+	u16 output_enable_bit;
+	u8 ext_power_reg;
+	u8 ext_power_int_reg;
+	u8 ext_power_bit;
+	u8 has_power;
+};
+
+struct xonar_hdmi {
+	u8 params[5];
+};
+
+/* generic helper functions */
+
+void xonar_enable_output(struct oxygen *chip);
+void xonar_disable_output(struct oxygen *chip);
+void xonar_init_ext_power(struct oxygen *chip);
+void xonar_init_cs53x1(struct oxygen *chip);
+void xonar_set_cs53x1_params(struct oxygen *chip,
+			     struct snd_pcm_hw_params *params);
+
+#define XONAR_GPIO_BIT_INVERT	(1 << 16)
+int xonar_gpio_bit_switch_get(struct snd_kcontrol *ctl,
+			      struct snd_ctl_elem_value *value);
+int xonar_gpio_bit_switch_put(struct snd_kcontrol *ctl,
+			      struct snd_ctl_elem_value *value);
+
+/* model-specific card drivers */
+
+int get_xonar_pcm179x_model(struct oxygen *chip,
+			    const struct pci_device_id *id);
+int get_xonar_cs43xx_model(struct oxygen *chip,
+			   const struct pci_device_id *id);
+int get_xonar_wm87x6_model(struct oxygen *chip,
+			   const struct pci_device_id *id);
+
+/* HDMI helper functions */
+
+void xonar_hdmi_init(struct oxygen *chip, struct xonar_hdmi *data);
+void xonar_hdmi_cleanup(struct oxygen *chip);
+void xonar_hdmi_resume(struct oxygen *chip, struct xonar_hdmi *hdmi);
+void xonar_hdmi_pcm_hardware_filter(unsigned int channel,
+				    struct snd_pcm_hardware *hardware);
+void xonar_set_hdmi_params(struct oxygen *chip, struct xonar_hdmi *hdmi,
+			   struct snd_pcm_hw_params *params);
+void xonar_hdmi_uart_input(struct oxygen *chip);
+
+#endif
diff --git a/sound/pci/oxygen/xonar_cs43xx.c b/sound/pci/oxygen/xonar_cs43xx.c
new file mode 100644
index 0000000..d231b93
--- /dev/null
+++ b/sound/pci/oxygen/xonar_cs43xx.c
@@ -0,0 +1,452 @@
+/*
+ * card driver for models with CS4398/CS4362A DACs (Xonar D1/DX)
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Xonar D1/DX
+ * -----------
+ *
+ * CMI8788:
+ *
+ *   I²C <-> CS4398 (addr 1001111) (front)
+ *       <-> CS4362A (addr 0011000) (surround, center/LFE, back)
+ *
+ *   GPI 0 <- external power present (DX only)
+ *
+ *   GPIO 0 -> enable output to speakers
+ *   GPIO 1 -> route output to front panel
+ *   GPIO 2 -> M0 of CS5361
+ *   GPIO 3 -> M1 of CS5361
+ *   GPIO 6 -> ?
+ *   GPIO 7 -> ?
+ *   GPIO 8 -> route input jack to line-in (0) or mic-in (1)
+ *
+ * CM9780:
+ *
+ *   LINE_OUT -> input of ADC
+ *
+ *   AUX_IN  <- aux
+ *   MIC_IN  <- mic
+ *   FMIC_IN <- front mic
+ *
+ *   GPO 0 -> route line-in (0) or AC97 output (1) to CS5361 input
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/ac97_codec.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include "xonar.h"
+#include "cm9780.h"
+#include "cs4398.h"
+#include "cs4362a.h"
+
+#define GPI_EXT_POWER		0x01
+#define GPIO_D1_OUTPUT_ENABLE	0x0001
+#define GPIO_D1_FRONT_PANEL	0x0002
+#define GPIO_D1_MAGIC		0x00c0
+#define GPIO_D1_INPUT_ROUTE	0x0100
+
+#define I2C_DEVICE_CS4398	0x9e	/* 10011, AD1=1, AD0=1, /W=0 */
+#define I2C_DEVICE_CS4362A	0x30	/* 001100, AD0=0, /W=0 */
+
+struct xonar_cs43xx {
+	struct xonar_generic generic;
+	u8 cs4398_regs[8];
+	u8 cs4362a_regs[15];
+};
+
+static void cs4398_write(struct oxygen *chip, u8 reg, u8 value)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+
+	oxygen_write_i2c(chip, I2C_DEVICE_CS4398, reg, value);
+	if (reg < ARRAY_SIZE(data->cs4398_regs))
+		data->cs4398_regs[reg] = value;
+}
+
+static void cs4398_write_cached(struct oxygen *chip, u8 reg, u8 value)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+
+	if (value != data->cs4398_regs[reg])
+		cs4398_write(chip, reg, value);
+}
+
+static void cs4362a_write(struct oxygen *chip, u8 reg, u8 value)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+
+	oxygen_write_i2c(chip, I2C_DEVICE_CS4362A, reg, value);
+	if (reg < ARRAY_SIZE(data->cs4362a_regs))
+		data->cs4362a_regs[reg] = value;
+}
+
+static void cs4362a_write_cached(struct oxygen *chip, u8 reg, u8 value)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+
+	if (value != data->cs4362a_regs[reg])
+		cs4362a_write(chip, reg, value);
+}
+
+static void cs43xx_registers_init(struct oxygen *chip)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+	unsigned int i;
+
+	/* set CPEN (control port mode) and power down */
+	cs4398_write(chip, 8, CS4398_CPEN | CS4398_PDN);
+	cs4362a_write(chip, 0x01, CS4362A_PDN | CS4362A_CPEN);
+	/* configure */
+	cs4398_write(chip, 2, data->cs4398_regs[2]);
+	cs4398_write(chip, 3, CS4398_ATAPI_B_R | CS4398_ATAPI_A_L);
+	cs4398_write(chip, 4, data->cs4398_regs[4]);
+	cs4398_write(chip, 5, data->cs4398_regs[5]);
+	cs4398_write(chip, 6, data->cs4398_regs[6]);
+	cs4398_write(chip, 7, data->cs4398_regs[7]);
+	cs4362a_write(chip, 0x02, CS4362A_DIF_LJUST);
+	cs4362a_write(chip, 0x03, CS4362A_MUTEC_6 | CS4362A_AMUTE |
+		      CS4362A_RMP_UP | CS4362A_ZERO_CROSS | CS4362A_SOFT_RAMP);
+	cs4362a_write(chip, 0x04, data->cs4362a_regs[0x04]);
+	cs4362a_write(chip, 0x05, 0);
+	for (i = 6; i <= 14; ++i)
+		cs4362a_write(chip, i, data->cs4362a_regs[i]);
+	/* clear power down */
+	cs4398_write(chip, 8, CS4398_CPEN);
+	cs4362a_write(chip, 0x01, CS4362A_CPEN);
+}
+
+static void xonar_d1_init(struct oxygen *chip)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+
+	data->generic.anti_pop_delay = 800;
+	data->generic.output_enable_bit = GPIO_D1_OUTPUT_ENABLE;
+	data->cs4398_regs[2] =
+		CS4398_FM_SINGLE | CS4398_DEM_NONE | CS4398_DIF_LJUST;
+	data->cs4398_regs[4] = CS4398_MUTEP_LOW |
+		CS4398_MUTE_B | CS4398_MUTE_A | CS4398_PAMUTE;
+	data->cs4398_regs[5] = 60 * 2;
+	data->cs4398_regs[6] = 60 * 2;
+	data->cs4398_regs[7] = CS4398_RMP_DN | CS4398_RMP_UP |
+		CS4398_ZERO_CROSS | CS4398_SOFT_RAMP;
+	data->cs4362a_regs[4] = CS4362A_RMP_DN | CS4362A_DEM_NONE;
+	data->cs4362a_regs[6] = CS4362A_FM_SINGLE |
+		CS4362A_ATAPI_B_R | CS4362A_ATAPI_A_L;
+	data->cs4362a_regs[7] = 60 | CS4362A_MUTE;
+	data->cs4362a_regs[8] = 60 | CS4362A_MUTE;
+	data->cs4362a_regs[9] = data->cs4362a_regs[6];
+	data->cs4362a_regs[10] = 60 | CS4362A_MUTE;
+	data->cs4362a_regs[11] = 60 | CS4362A_MUTE;
+	data->cs4362a_regs[12] = data->cs4362a_regs[6];
+	data->cs4362a_regs[13] = 60 | CS4362A_MUTE;
+	data->cs4362a_regs[14] = 60 | CS4362A_MUTE;
+
+	oxygen_write16(chip, OXYGEN_2WIRE_BUS_STATUS,
+		       OXYGEN_2WIRE_LENGTH_8 |
+		       OXYGEN_2WIRE_INTERRUPT_MASK |
+		       OXYGEN_2WIRE_SPEED_FAST);
+
+	cs43xx_registers_init(chip);
+
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL,
+			  GPIO_D1_FRONT_PANEL |
+			  GPIO_D1_MAGIC |
+			  GPIO_D1_INPUT_ROUTE);
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA,
+			    GPIO_D1_FRONT_PANEL | GPIO_D1_INPUT_ROUTE);
+
+	xonar_init_cs53x1(chip);
+	xonar_enable_output(chip);
+
+	snd_component_add(chip->card, "CS4398");
+	snd_component_add(chip->card, "CS4362A");
+	snd_component_add(chip->card, "CS5361");
+}
+
+static void xonar_dx_init(struct oxygen *chip)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+
+	data->generic.ext_power_reg = OXYGEN_GPI_DATA;
+	data->generic.ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK;
+	data->generic.ext_power_bit = GPI_EXT_POWER;
+	xonar_init_ext_power(chip);
+	xonar_d1_init(chip);
+}
+
+static void xonar_d1_cleanup(struct oxygen *chip)
+{
+	xonar_disable_output(chip);
+	cs4362a_write(chip, 0x01, CS4362A_PDN | CS4362A_CPEN);
+	oxygen_clear_bits8(chip, OXYGEN_FUNCTION, OXYGEN_FUNCTION_RESET_CODEC);
+}
+
+static void xonar_d1_suspend(struct oxygen *chip)
+{
+	xonar_d1_cleanup(chip);
+}
+
+static void xonar_d1_resume(struct oxygen *chip)
+{
+	oxygen_set_bits8(chip, OXYGEN_FUNCTION, OXYGEN_FUNCTION_RESET_CODEC);
+	msleep(1);
+	cs43xx_registers_init(chip);
+	xonar_enable_output(chip);
+}
+
+static void set_cs43xx_params(struct oxygen *chip,
+			      struct snd_pcm_hw_params *params)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+	u8 cs4398_fm, cs4362a_fm;
+
+	if (params_rate(params) <= 50000) {
+		cs4398_fm = CS4398_FM_SINGLE;
+		cs4362a_fm = CS4362A_FM_SINGLE;
+	} else if (params_rate(params) <= 100000) {
+		cs4398_fm = CS4398_FM_DOUBLE;
+		cs4362a_fm = CS4362A_FM_DOUBLE;
+	} else {
+		cs4398_fm = CS4398_FM_QUAD;
+		cs4362a_fm = CS4362A_FM_QUAD;
+	}
+	cs4398_fm |= CS4398_DEM_NONE | CS4398_DIF_LJUST;
+	cs4398_write_cached(chip, 2, cs4398_fm);
+	cs4362a_fm |= data->cs4362a_regs[6] & ~CS4362A_FM_MASK;
+	cs4362a_write_cached(chip, 6, cs4362a_fm);
+	cs4362a_write_cached(chip, 12, cs4362a_fm);
+	cs4362a_fm &= CS4362A_FM_MASK;
+	cs4362a_fm |= data->cs4362a_regs[9] & ~CS4362A_FM_MASK;
+	cs4362a_write_cached(chip, 9, cs4362a_fm);
+}
+
+static void update_cs4362a_volumes(struct oxygen *chip)
+{
+	unsigned int i;
+	u8 mute;
+
+	mute = chip->dac_mute ? CS4362A_MUTE : 0;
+	for (i = 0; i < 6; ++i)
+		cs4362a_write_cached(chip, 7 + i + i / 2,
+				     (127 - chip->dac_volume[2 + i]) | mute);
+}
+
+static void update_cs43xx_volume(struct oxygen *chip)
+{
+	cs4398_write_cached(chip, 5, (127 - chip->dac_volume[0]) * 2);
+	cs4398_write_cached(chip, 6, (127 - chip->dac_volume[1]) * 2);
+	update_cs4362a_volumes(chip);
+}
+
+static void update_cs43xx_mute(struct oxygen *chip)
+{
+	u8 reg;
+
+	reg = CS4398_MUTEP_LOW | CS4398_PAMUTE;
+	if (chip->dac_mute)
+		reg |= CS4398_MUTE_B | CS4398_MUTE_A;
+	cs4398_write_cached(chip, 4, reg);
+	update_cs4362a_volumes(chip);
+}
+
+static void update_cs43xx_center_lfe_mix(struct oxygen *chip, bool mixed)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+	u8 reg;
+
+	reg = data->cs4362a_regs[9] & ~CS4362A_ATAPI_MASK;
+	if (mixed)
+		reg |= CS4362A_ATAPI_B_LR | CS4362A_ATAPI_A_LR;
+	else
+		reg |= CS4362A_ATAPI_B_R | CS4362A_ATAPI_A_L;
+	cs4362a_write_cached(chip, 9, reg);
+}
+
+static const struct snd_kcontrol_new front_panel_switch = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Front Panel Playback Switch",
+	.info = snd_ctl_boolean_mono_info,
+	.get = xonar_gpio_bit_switch_get,
+	.put = xonar_gpio_bit_switch_put,
+	.private_value = GPIO_D1_FRONT_PANEL,
+};
+
+static int rolloff_info(struct snd_kcontrol *ctl,
+			struct snd_ctl_elem_info *info)
+{
+	static const char *const names[2] = {
+		"Fast Roll-off", "Slow Roll-off"
+	};
+
+	return snd_ctl_enum_info(info, 1, 2, names);
+}
+
+static int rolloff_get(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_cs43xx *data = chip->model_data;
+
+	value->value.enumerated.item[0] =
+		(data->cs4398_regs[7] & CS4398_FILT_SEL) != 0;
+	return 0;
+}
+
+static int rolloff_put(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_cs43xx *data = chip->model_data;
+	int changed;
+	u8 reg;
+
+	mutex_lock(&chip->mutex);
+	reg = data->cs4398_regs[7];
+	if (value->value.enumerated.item[0])
+		reg |= CS4398_FILT_SEL;
+	else
+		reg &= ~CS4398_FILT_SEL;
+	changed = reg != data->cs4398_regs[7];
+	if (changed) {
+		cs4398_write(chip, 7, reg);
+		if (reg & CS4398_FILT_SEL)
+			reg = data->cs4362a_regs[0x04] | CS4362A_FILT_SEL;
+		else
+			reg = data->cs4362a_regs[0x04] & ~CS4362A_FILT_SEL;
+		cs4362a_write(chip, 0x04, reg);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static const struct snd_kcontrol_new rolloff_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "DAC Filter Playback Enum",
+	.info = rolloff_info,
+	.get = rolloff_get,
+	.put = rolloff_put,
+};
+
+static void xonar_d1_line_mic_ac97_switch(struct oxygen *chip,
+					  unsigned int reg, unsigned int mute)
+{
+	if (reg == AC97_LINE) {
+		spin_lock_irq(&chip->reg_lock);
+		oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+				      mute ? GPIO_D1_INPUT_ROUTE : 0,
+				      GPIO_D1_INPUT_ROUTE);
+		spin_unlock_irq(&chip->reg_lock);
+	}
+}
+
+static const DECLARE_TLV_DB_SCALE(cs4362a_db_scale, -6000, 100, 0);
+
+static int xonar_d1_mixer_init(struct oxygen *chip)
+{
+	int err;
+
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&front_panel_switch, chip));
+	if (err < 0)
+		return err;
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&rolloff_control, chip));
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static void dump_cs4362a_registers(struct xonar_cs43xx *data,
+				   struct snd_info_buffer *buffer)
+{
+	unsigned int i;
+
+	snd_iprintf(buffer, "\nCS4362A:");
+	for (i = 1; i <= 14; ++i)
+		snd_iprintf(buffer, " %02x", data->cs4362a_regs[i]);
+	snd_iprintf(buffer, "\n");
+}
+
+static void dump_d1_registers(struct oxygen *chip,
+			      struct snd_info_buffer *buffer)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+	unsigned int i;
+
+	snd_iprintf(buffer, "\nCS4398: 7?");
+	for (i = 2; i < 8; ++i)
+		snd_iprintf(buffer, " %02x", data->cs4398_regs[i]);
+	snd_iprintf(buffer, "\n");
+	dump_cs4362a_registers(data, buffer);
+}
+
+static const struct oxygen_model model_xonar_d1 = {
+	.longname = "Asus Virtuoso 100",
+	.chip = "AV200",
+	.init = xonar_d1_init,
+	.mixer_init = xonar_d1_mixer_init,
+	.cleanup = xonar_d1_cleanup,
+	.suspend = xonar_d1_suspend,
+	.resume = xonar_d1_resume,
+	.set_dac_params = set_cs43xx_params,
+	.set_adc_params = xonar_set_cs53x1_params,
+	.update_dac_volume = update_cs43xx_volume,
+	.update_dac_mute = update_cs43xx_mute,
+	.update_center_lfe_mix = update_cs43xx_center_lfe_mix,
+	.ac97_switch = xonar_d1_line_mic_ac97_switch,
+	.dump_registers = dump_d1_registers,
+	.dac_tlv = cs4362a_db_scale,
+	.model_data_size = sizeof(struct xonar_cs43xx),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 CAPTURE_0_FROM_I2S_2 |
+			 CAPTURE_1_FROM_SPDIF |
+			 AC97_FMIC_SWITCH,
+	.dac_channels_pcm = 8,
+	.dac_channels_mixer = 8,
+	.dac_volume_min = 127 - 60,
+	.dac_volume_max = 127,
+	.function_flags = OXYGEN_FUNCTION_2WIRE,
+	.dac_mclks = OXYGEN_MCLKS(256, 128, 128),
+	.adc_mclks = OXYGEN_MCLKS(256, 128, 128),
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
+
+int get_xonar_cs43xx_model(struct oxygen *chip,
+			   const struct pci_device_id *id)
+{
+	switch (id->subdevice) {
+	case 0x834f:
+		chip->model = model_xonar_d1;
+		chip->model.shortname = "Xonar D1";
+		break;
+	case 0x8275:
+	case 0x8327:
+		chip->model = model_xonar_d1;
+		chip->model.shortname = "Xonar DX";
+		chip->model.init = xonar_dx_init;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
diff --git a/sound/pci/oxygen/xonar_dg.c b/sound/pci/oxygen/xonar_dg.c
new file mode 100644
index 0000000..4cf3200
--- /dev/null
+++ b/sound/pci/oxygen/xonar_dg.c
@@ -0,0 +1,295 @@
+/*
+ * card driver for the Xonar DG/DGX
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Copyright (c) Roman Volkov <v1ron@mail.ru>
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Xonar DG/DGX
+ * ------------
+ *
+ * CS4245 and CS4361 both will mute all outputs if any clock ratio
+ * is invalid.
+ *
+ * CMI8788:
+ *
+ *   SPI 0 -> CS4245
+ *
+ *   Playback:
+ *   I²S 1 -> CS4245
+ *   I²S 2 -> CS4361 (center/LFE)
+ *   I²S 3 -> CS4361 (surround)
+ *   I²S 4 -> CS4361 (front)
+ *   Capture:
+ *   I²S ADC 1 <- CS4245
+ *
+ *   GPIO 3 <- ?
+ *   GPIO 4 <- headphone detect
+ *   GPIO 5 -> enable ADC analog circuit for the left channel
+ *   GPIO 6 -> enable ADC analog circuit for the right channel
+ *   GPIO 7 -> switch green rear output jack between CS4245 and and the first
+ *             channel of CS4361 (mechanical relay)
+ *   GPIO 8 -> enable output to speakers
+ *
+ * CS4245:
+ *
+ *   input 0 <- mic
+ *   input 1 <- aux
+ *   input 2 <- front mic
+ *   input 4 <- line
+ *   DAC out -> headphones
+ *   aux out -> front panel headphones
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+#include "oxygen.h"
+#include "xonar_dg.h"
+#include "cs4245.h"
+
+int cs4245_write_spi(struct oxygen *chip, u8 reg)
+{
+	struct dg *data = chip->model_data;
+	unsigned int packet;
+
+	packet = reg << 8;
+	packet |= (CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 16;
+	packet |= data->cs4245_shadow[reg];
+
+	return oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+				OXYGEN_SPI_DATA_LENGTH_3 |
+				OXYGEN_SPI_CLOCK_1280 |
+				(0 << OXYGEN_SPI_CODEC_SHIFT) |
+				OXYGEN_SPI_CEN_LATCH_CLOCK_HI,
+				packet);
+}
+
+int cs4245_read_spi(struct oxygen *chip, u8 addr)
+{
+	struct dg *data = chip->model_data;
+	int ret;
+
+	ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+		OXYGEN_SPI_DATA_LENGTH_2 |
+		OXYGEN_SPI_CEN_LATCH_CLOCK_HI |
+		OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT),
+		((CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 8) | addr);
+	if (ret < 0)
+		return ret;
+
+	ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+		OXYGEN_SPI_DATA_LENGTH_2 |
+		OXYGEN_SPI_CEN_LATCH_CLOCK_HI |
+		OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT),
+		(CS4245_SPI_ADDRESS | CS4245_SPI_READ) << 8);
+	if (ret < 0)
+		return ret;
+
+	data->cs4245_shadow[addr] = oxygen_read8(chip, OXYGEN_SPI_DATA1);
+
+	return 0;
+}
+
+int cs4245_shadow_control(struct oxygen *chip, enum cs4245_shadow_operation op)
+{
+	struct dg *data = chip->model_data;
+	unsigned char addr;
+	int ret;
+
+	for (addr = 1; addr < ARRAY_SIZE(data->cs4245_shadow); addr++) {
+		ret = (op == CS4245_SAVE_TO_SHADOW ?
+			cs4245_read_spi(chip, addr) :
+			cs4245_write_spi(chip, addr));
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+
+static void cs4245_init(struct oxygen *chip)
+{
+	struct dg *data = chip->model_data;
+
+	/* save the initial state: codec version, registers */
+	cs4245_shadow_control(chip, CS4245_SAVE_TO_SHADOW);
+
+	/*
+	 * Power up the CODEC internals, enable soft ramp & zero cross, work in
+	 * async. mode, enable aux output from DAC. Invert DAC output as in the
+	 * Windows driver.
+	 */
+	data->cs4245_shadow[CS4245_POWER_CTRL] = 0;
+	data->cs4245_shadow[CS4245_SIGNAL_SEL] =
+		CS4245_A_OUT_SEL_DAC | CS4245_ASYNCH;
+	data->cs4245_shadow[CS4245_DAC_CTRL_1] =
+		CS4245_DAC_FM_SINGLE | CS4245_DAC_DIF_LJUST;
+	data->cs4245_shadow[CS4245_DAC_CTRL_2] =
+		CS4245_DAC_SOFT | CS4245_DAC_ZERO | CS4245_INVERT_DAC;
+	data->cs4245_shadow[CS4245_ADC_CTRL] =
+		CS4245_ADC_FM_SINGLE | CS4245_ADC_DIF_LJUST;
+	data->cs4245_shadow[CS4245_ANALOG_IN] =
+		CS4245_PGA_SOFT | CS4245_PGA_ZERO;
+	data->cs4245_shadow[CS4245_PGA_B_CTRL] = 0;
+	data->cs4245_shadow[CS4245_PGA_A_CTRL] = 0;
+	data->cs4245_shadow[CS4245_DAC_A_CTRL] = 8;
+	data->cs4245_shadow[CS4245_DAC_B_CTRL] = 8;
+
+	cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW);
+	snd_component_add(chip->card, "CS4245");
+}
+
+void dg_init(struct oxygen *chip)
+{
+	struct dg *data = chip->model_data;
+
+	data->output_sel = PLAYBACK_DST_HP_FP;
+	data->input_sel = CAPTURE_SRC_MIC;
+
+	cs4245_init(chip);
+	oxygen_write16(chip, OXYGEN_GPIO_CONTROL,
+		       GPIO_OUTPUT_ENABLE | GPIO_HP_REAR | GPIO_INPUT_ROUTE);
+	/* anti-pop delay, wait some time before enabling the output */
+	msleep(2500);
+	oxygen_write16(chip, OXYGEN_GPIO_DATA,
+		       GPIO_OUTPUT_ENABLE | GPIO_INPUT_ROUTE);
+}
+
+void dg_cleanup(struct oxygen *chip)
+{
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE);
+}
+
+void dg_suspend(struct oxygen *chip)
+{
+	dg_cleanup(chip);
+}
+
+void dg_resume(struct oxygen *chip)
+{
+	cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW);
+	msleep(2500);
+	oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE);
+}
+
+void set_cs4245_dac_params(struct oxygen *chip,
+				  struct snd_pcm_hw_params *params)
+{
+	struct dg *data = chip->model_data;
+	unsigned char dac_ctrl;
+	unsigned char mclk_freq;
+
+	dac_ctrl = data->cs4245_shadow[CS4245_DAC_CTRL_1] & ~CS4245_DAC_FM_MASK;
+	mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK1_MASK;
+	if (params_rate(params) <= 50000) {
+		dac_ctrl |= CS4245_DAC_FM_SINGLE;
+		mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT;
+	} else if (params_rate(params) <= 100000) {
+		dac_ctrl |= CS4245_DAC_FM_DOUBLE;
+		mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT;
+	} else {
+		dac_ctrl |= CS4245_DAC_FM_QUAD;
+		mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK1_SHIFT;
+	}
+	data->cs4245_shadow[CS4245_DAC_CTRL_1] = dac_ctrl;
+	data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq;
+	cs4245_write_spi(chip, CS4245_DAC_CTRL_1);
+	cs4245_write_spi(chip, CS4245_MCLK_FREQ);
+}
+
+void set_cs4245_adc_params(struct oxygen *chip,
+				  struct snd_pcm_hw_params *params)
+{
+	struct dg *data = chip->model_data;
+	unsigned char adc_ctrl;
+	unsigned char mclk_freq;
+
+	adc_ctrl = data->cs4245_shadow[CS4245_ADC_CTRL] & ~CS4245_ADC_FM_MASK;
+	mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK2_MASK;
+	if (params_rate(params) <= 50000) {
+		adc_ctrl |= CS4245_ADC_FM_SINGLE;
+		mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT;
+	} else if (params_rate(params) <= 100000) {
+		adc_ctrl |= CS4245_ADC_FM_DOUBLE;
+		mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT;
+	} else {
+		adc_ctrl |= CS4245_ADC_FM_QUAD;
+		mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK2_SHIFT;
+	}
+	data->cs4245_shadow[CS4245_ADC_CTRL] = adc_ctrl;
+	data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq;
+	cs4245_write_spi(chip, CS4245_ADC_CTRL);
+	cs4245_write_spi(chip, CS4245_MCLK_FREQ);
+}
+
+static inline unsigned int shift_bits(unsigned int value,
+				      unsigned int shift_from,
+				      unsigned int shift_to,
+				      unsigned int mask)
+{
+	if (shift_from < shift_to)
+		return (value << (shift_to - shift_from)) & mask;
+	else
+		return (value >> (shift_from - shift_to)) & mask;
+}
+
+unsigned int adjust_dg_dac_routing(struct oxygen *chip,
+					  unsigned int play_routing)
+{
+	struct dg *data = chip->model_data;
+
+	switch (data->output_sel) {
+	case PLAYBACK_DST_HP:
+	case PLAYBACK_DST_HP_FP:
+		oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING,
+			OXYGEN_PLAY_MUTE23 | OXYGEN_PLAY_MUTE45 |
+			OXYGEN_PLAY_MUTE67, OXYGEN_PLAY_MUTE_MASK);
+		break;
+	case PLAYBACK_DST_MULTICH:
+		oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING,
+			OXYGEN_PLAY_MUTE01, OXYGEN_PLAY_MUTE_MASK);
+		break;
+	}
+	return (play_routing & OXYGEN_PLAY_DAC0_SOURCE_MASK) |
+	       shift_bits(play_routing,
+			  OXYGEN_PLAY_DAC2_SOURCE_SHIFT,
+			  OXYGEN_PLAY_DAC1_SOURCE_SHIFT,
+			  OXYGEN_PLAY_DAC1_SOURCE_MASK) |
+	       shift_bits(play_routing,
+			  OXYGEN_PLAY_DAC1_SOURCE_SHIFT,
+			  OXYGEN_PLAY_DAC2_SOURCE_SHIFT,
+			  OXYGEN_PLAY_DAC2_SOURCE_MASK) |
+	       shift_bits(play_routing,
+			  OXYGEN_PLAY_DAC0_SOURCE_SHIFT,
+			  OXYGEN_PLAY_DAC3_SOURCE_SHIFT,
+			  OXYGEN_PLAY_DAC3_SOURCE_MASK);
+}
+
+void dump_cs4245_registers(struct oxygen *chip,
+				  struct snd_info_buffer *buffer)
+{
+	struct dg *data = chip->model_data;
+	unsigned int addr;
+
+	snd_iprintf(buffer, "\nCS4245:");
+	cs4245_read_spi(chip, CS4245_INT_STATUS);
+	for (addr = 1; addr < ARRAY_SIZE(data->cs4245_shadow); addr++)
+		snd_iprintf(buffer, " %02x", data->cs4245_shadow[addr]);
+	snd_iprintf(buffer, "\n");
+}
diff --git a/sound/pci/oxygen/xonar_dg.h b/sound/pci/oxygen/xonar_dg.h
new file mode 100644
index 0000000..24d9772
--- /dev/null
+++ b/sound/pci/oxygen/xonar_dg.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef XONAR_DG_H_INCLUDED
+#define XONAR_DG_H_INCLUDED
+
+#include "oxygen.h"
+
+#define GPIO_MAGIC		0x0008
+#define GPIO_HP_DETECT		0x0010
+#define GPIO_INPUT_ROUTE	0x0060
+#define GPIO_HP_REAR		0x0080
+#define GPIO_OUTPUT_ENABLE	0x0100
+
+#define CAPTURE_SRC_MIC		0
+#define CAPTURE_SRC_FP_MIC	1
+#define CAPTURE_SRC_LINE	2
+#define CAPTURE_SRC_AUX		3
+
+#define PLAYBACK_DST_HP		0
+#define PLAYBACK_DST_HP_FP	1
+#define PLAYBACK_DST_MULTICH	2
+
+enum cs4245_shadow_operation {
+	CS4245_SAVE_TO_SHADOW,
+	CS4245_LOAD_FROM_SHADOW
+};
+
+struct dg {
+	/* shadow copy of the CS4245 register space */
+	unsigned char cs4245_shadow[17];
+	/* output select: headphone/speakers */
+	unsigned char output_sel;
+	/* volumes for all capture sources */
+	char input_vol[4][2];
+	/* input select: mic/fp mic/line/aux */
+	unsigned char input_sel;
+};
+
+/* Xonar DG control routines */
+int cs4245_write_spi(struct oxygen *chip, u8 reg);
+int cs4245_read_spi(struct oxygen *chip, u8 reg);
+int cs4245_shadow_control(struct oxygen *chip, enum cs4245_shadow_operation op);
+void dg_init(struct oxygen *chip);
+void set_cs4245_dac_params(struct oxygen *chip,
+				  struct snd_pcm_hw_params *params);
+void set_cs4245_adc_params(struct oxygen *chip,
+				  struct snd_pcm_hw_params *params);
+unsigned int adjust_dg_dac_routing(struct oxygen *chip,
+					  unsigned int play_routing);
+void dump_cs4245_registers(struct oxygen *chip,
+				struct snd_info_buffer *buffer);
+void dg_suspend(struct oxygen *chip);
+void dg_resume(struct oxygen *chip);
+void dg_cleanup(struct oxygen *chip);
+
+extern const struct oxygen_model model_xonar_dg;
+
+#endif
diff --git a/sound/pci/oxygen/xonar_dg_mixer.c b/sound/pci/oxygen/xonar_dg_mixer.c
new file mode 100644
index 0000000..d22fbe8
--- /dev/null
+++ b/sound/pci/oxygen/xonar_dg_mixer.c
@@ -0,0 +1,477 @@
+/*
+ * Mixer controls for the Xonar DG/DGX
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Copyright (c) Roman Volkov <v1ron@mail.ru>
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+#include "oxygen.h"
+#include "xonar_dg.h"
+#include "cs4245.h"
+
+/* analog output select */
+
+static int output_select_apply(struct oxygen *chip)
+{
+	struct dg *data = chip->model_data;
+
+	data->cs4245_shadow[CS4245_SIGNAL_SEL] &= ~CS4245_A_OUT_SEL_MASK;
+	if (data->output_sel == PLAYBACK_DST_HP) {
+		/* mute FP (aux output) amplifier, switch rear jack to CS4245 */
+		oxygen_set_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR);
+	} else if (data->output_sel == PLAYBACK_DST_HP_FP) {
+		/*
+		 * Unmute FP amplifier, switch rear jack to CS4361;
+		 * I2S channels 2,3,4 should be inactive.
+		 */
+		oxygen_clear_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR);
+		data->cs4245_shadow[CS4245_SIGNAL_SEL] |= CS4245_A_OUT_SEL_DAC;
+	} else {
+		/*
+		 * 2.0, 4.0, 5.1: switch to CS4361, mute FP amp.,
+		 * and change playback routing.
+		 */
+		oxygen_clear_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR);
+	}
+	return cs4245_write_spi(chip, CS4245_SIGNAL_SEL);
+}
+
+static int output_select_info(struct snd_kcontrol *ctl,
+			      struct snd_ctl_elem_info *info)
+{
+	static const char *const names[3] = {
+		"Stereo Headphones",
+		"Stereo Headphones FP",
+		"Multichannel",
+	};
+
+	return snd_ctl_enum_info(info, 1, 3, names);
+}
+
+static int output_select_get(struct snd_kcontrol *ctl,
+			     struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct dg *data = chip->model_data;
+
+	mutex_lock(&chip->mutex);
+	value->value.enumerated.item[0] = data->output_sel;
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int output_select_put(struct snd_kcontrol *ctl,
+			     struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct dg *data = chip->model_data;
+	unsigned int new = value->value.enumerated.item[0];
+	int changed = 0;
+	int ret;
+
+	mutex_lock(&chip->mutex);
+	if (data->output_sel != new) {
+		data->output_sel = new;
+		ret = output_select_apply(chip);
+		changed = ret >= 0 ? 1 : ret;
+		oxygen_update_dac_routing(chip);
+	}
+	mutex_unlock(&chip->mutex);
+
+	return changed;
+}
+
+/* CS4245 Headphone Channels A&B Volume Control */
+
+static int hp_stereo_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 = 255;
+	return 0;
+}
+
+static int hp_stereo_volume_get(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *val)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct dg *data = chip->model_data;
+	unsigned int tmp;
+
+	mutex_lock(&chip->mutex);
+	tmp = (~data->cs4245_shadow[CS4245_DAC_A_CTRL]) & 255;
+	val->value.integer.value[0] = tmp;
+	tmp = (~data->cs4245_shadow[CS4245_DAC_B_CTRL]) & 255;
+	val->value.integer.value[1] = tmp;
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int hp_stereo_volume_put(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *val)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct dg *data = chip->model_data;
+	int ret;
+	int changed = 0;
+	long new1 = val->value.integer.value[0];
+	long new2 = val->value.integer.value[1];
+
+	if ((new1 > 255) || (new1 < 0) || (new2 > 255) || (new2 < 0))
+		return -EINVAL;
+
+	mutex_lock(&chip->mutex);
+	if ((data->cs4245_shadow[CS4245_DAC_A_CTRL] != ~new1) ||
+	    (data->cs4245_shadow[CS4245_DAC_B_CTRL] != ~new2)) {
+		data->cs4245_shadow[CS4245_DAC_A_CTRL] = ~new1;
+		data->cs4245_shadow[CS4245_DAC_B_CTRL] = ~new2;
+		ret = cs4245_write_spi(chip, CS4245_DAC_A_CTRL);
+		if (ret >= 0)
+			ret = cs4245_write_spi(chip, CS4245_DAC_B_CTRL);
+		changed = ret >= 0 ? 1 : ret;
+	}
+	mutex_unlock(&chip->mutex);
+
+	return changed;
+}
+
+/* Headphone Mute */
+
+static int hp_mute_get(struct snd_kcontrol *ctl,
+			struct snd_ctl_elem_value *val)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct dg *data = chip->model_data;
+
+	mutex_lock(&chip->mutex);
+	val->value.integer.value[0] =
+		!(data->cs4245_shadow[CS4245_DAC_CTRL_1] & CS4245_MUTE_DAC);
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int hp_mute_put(struct snd_kcontrol *ctl,
+			struct snd_ctl_elem_value *val)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct dg *data = chip->model_data;
+	int ret;
+	int changed;
+
+	if (val->value.integer.value[0] > 1)
+		return -EINVAL;
+	mutex_lock(&chip->mutex);
+	data->cs4245_shadow[CS4245_DAC_CTRL_1] &= ~CS4245_MUTE_DAC;
+	data->cs4245_shadow[CS4245_DAC_CTRL_1] |=
+		(~val->value.integer.value[0] << 2) & CS4245_MUTE_DAC;
+	ret = cs4245_write_spi(chip, CS4245_DAC_CTRL_1);
+	changed = ret >= 0 ? 1 : ret;
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+/* capture volume for all sources */
+
+static int input_volume_apply(struct oxygen *chip, char left, char right)
+{
+	struct dg *data = chip->model_data;
+	int ret;
+
+	data->cs4245_shadow[CS4245_PGA_A_CTRL] = left;
+	data->cs4245_shadow[CS4245_PGA_B_CTRL] = right;
+	ret = cs4245_write_spi(chip, CS4245_PGA_A_CTRL);
+	if (ret < 0)
+		return ret;
+	return cs4245_write_spi(chip, CS4245_PGA_B_CTRL);
+}
+
+static int input_vol_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 = 2 * -12;
+	info->value.integer.max = 2 * 12;
+	return 0;
+}
+
+static int input_vol_get(struct snd_kcontrol *ctl,
+			 struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct dg *data = chip->model_data;
+	unsigned int idx = ctl->private_value;
+
+	mutex_lock(&chip->mutex);
+	value->value.integer.value[0] = data->input_vol[idx][0];
+	value->value.integer.value[1] = data->input_vol[idx][1];
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int input_vol_put(struct snd_kcontrol *ctl,
+			 struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct dg *data = chip->model_data;
+	unsigned int idx = ctl->private_value;
+	int changed = 0;
+	int ret = 0;
+
+	if (value->value.integer.value[0] < 2 * -12 ||
+	    value->value.integer.value[0] > 2 * 12 ||
+	    value->value.integer.value[1] < 2 * -12 ||
+	    value->value.integer.value[1] > 2 * 12)
+		return -EINVAL;
+	mutex_lock(&chip->mutex);
+	changed = data->input_vol[idx][0] != value->value.integer.value[0] ||
+		  data->input_vol[idx][1] != value->value.integer.value[1];
+	if (changed) {
+		data->input_vol[idx][0] = value->value.integer.value[0];
+		data->input_vol[idx][1] = value->value.integer.value[1];
+		if (idx == data->input_sel) {
+			ret = input_volume_apply(chip,
+				data->input_vol[idx][0],
+				data->input_vol[idx][1]);
+		}
+		changed = ret >= 0 ? 1 : ret;
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+/* Capture Source */
+
+static int input_source_apply(struct oxygen *chip)
+{
+	struct dg *data = chip->model_data;
+
+	data->cs4245_shadow[CS4245_ANALOG_IN] &= ~CS4245_SEL_MASK;
+	if (data->input_sel == CAPTURE_SRC_FP_MIC)
+		data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_2;
+	else if (data->input_sel == CAPTURE_SRC_LINE)
+		data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_4;
+	else if (data->input_sel != CAPTURE_SRC_MIC)
+		data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_1;
+	return cs4245_write_spi(chip, CS4245_ANALOG_IN);
+}
+
+static int input_sel_info(struct snd_kcontrol *ctl,
+			  struct snd_ctl_elem_info *info)
+{
+	static const char *const names[4] = {
+		"Mic", "Front Mic", "Line", "Aux"
+	};
+
+	return snd_ctl_enum_info(info, 1, 4, names);
+}
+
+static int input_sel_get(struct snd_kcontrol *ctl,
+			 struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct dg *data = chip->model_data;
+
+	mutex_lock(&chip->mutex);
+	value->value.enumerated.item[0] = data->input_sel;
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int input_sel_put(struct snd_kcontrol *ctl,
+			 struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct dg *data = chip->model_data;
+	int changed;
+	int ret;
+
+	if (value->value.enumerated.item[0] > 3)
+		return -EINVAL;
+
+	mutex_lock(&chip->mutex);
+	changed = value->value.enumerated.item[0] != data->input_sel;
+	if (changed) {
+		data->input_sel = value->value.enumerated.item[0];
+
+		ret = input_source_apply(chip);
+		if (ret >= 0)
+			ret = input_volume_apply(chip,
+				data->input_vol[data->input_sel][0],
+				data->input_vol[data->input_sel][1]);
+		changed = ret >= 0 ? 1 : ret;
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+/* ADC high-pass filter */
+
+static int hpf_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)
+{
+	static const char *const names[2] = { "Active", "Frozen" };
+
+	return snd_ctl_enum_info(info, 1, 2, names);
+}
+
+static int hpf_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct dg *data = chip->model_data;
+
+	value->value.enumerated.item[0] =
+		!!(data->cs4245_shadow[CS4245_ADC_CTRL] & CS4245_HPF_FREEZE);
+	return 0;
+}
+
+static int hpf_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct dg *data = chip->model_data;
+	u8 reg;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	reg = data->cs4245_shadow[CS4245_ADC_CTRL] & ~CS4245_HPF_FREEZE;
+	if (value->value.enumerated.item[0])
+		reg |= CS4245_HPF_FREEZE;
+	changed = reg != data->cs4245_shadow[CS4245_ADC_CTRL];
+	if (changed) {
+		data->cs4245_shadow[CS4245_ADC_CTRL] = reg;
+		cs4245_write_spi(chip, CS4245_ADC_CTRL);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+#define INPUT_VOLUME(xname, index) { \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = xname, \
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+		  SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+	.info = input_vol_info, \
+	.get = input_vol_get, \
+	.put = input_vol_put, \
+	.tlv = { .p = pga_db_scale }, \
+	.private_value = index, \
+}
+static const DECLARE_TLV_DB_MINMAX(hp_db_scale, -12550, 0);
+static const DECLARE_TLV_DB_MINMAX(pga_db_scale, -1200, 1200);
+static const struct snd_kcontrol_new dg_controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Output Playback Enum",
+		.info = output_select_info,
+		.get = output_select_get,
+		.put = output_select_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Headphone Playback Volume",
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+		.info = hp_stereo_volume_info,
+		.get = hp_stereo_volume_get,
+		.put = hp_stereo_volume_put,
+		.tlv = { .p = hp_db_scale, },
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Headphone Playback Switch",
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+		.info = snd_ctl_boolean_mono_info,
+		.get = hp_mute_get,
+		.put = hp_mute_put,
+	},
+	INPUT_VOLUME("Mic Capture Volume", CAPTURE_SRC_MIC),
+	INPUT_VOLUME("Front Mic Capture Volume", CAPTURE_SRC_FP_MIC),
+	INPUT_VOLUME("Line Capture Volume", CAPTURE_SRC_LINE),
+	INPUT_VOLUME("Aux Capture Volume", CAPTURE_SRC_AUX),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Capture Source",
+		.info = input_sel_info,
+		.get = input_sel_get,
+		.put = input_sel_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "ADC High-pass Filter Capture Enum",
+		.info = hpf_info,
+		.get = hpf_get,
+		.put = hpf_put,
+	},
+};
+
+static int dg_control_filter(struct snd_kcontrol_new *template)
+{
+	if (!strncmp(template->name, "Master Playback ", 16))
+		return 1;
+	return 0;
+}
+
+static int dg_mixer_init(struct oxygen *chip)
+{
+	unsigned int i;
+	int err;
+
+	output_select_apply(chip);
+	input_source_apply(chip);
+	oxygen_update_dac_routing(chip);
+
+	for (i = 0; i < ARRAY_SIZE(dg_controls); ++i) {
+		err = snd_ctl_add(chip->card,
+				  snd_ctl_new1(&dg_controls[i], chip));
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+const struct oxygen_model model_xonar_dg = {
+	.longname = "C-Media Oxygen HD Audio",
+	.chip = "CMI8786",
+	.init = dg_init,
+	.control_filter = dg_control_filter,
+	.mixer_init = dg_mixer_init,
+	.cleanup = dg_cleanup,
+	.suspend = dg_suspend,
+	.resume = dg_resume,
+	.set_dac_params = set_cs4245_dac_params,
+	.set_adc_params = set_cs4245_adc_params,
+	.adjust_dac_routing = adjust_dg_dac_routing,
+	.dump_registers = dump_cs4245_registers,
+	.model_data_size = sizeof(struct dg),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 CAPTURE_0_FROM_I2S_1 |
+			 CAPTURE_1_FROM_SPDIF,
+	.dac_channels_pcm = 6,
+	.dac_channels_mixer = 0,
+	.function_flags = OXYGEN_FUNCTION_SPI,
+	.dac_mclks = OXYGEN_MCLKS(256, 128, 128),
+	.adc_mclks = OXYGEN_MCLKS(256, 128, 128),
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
diff --git a/sound/pci/oxygen/xonar_hdmi.c b/sound/pci/oxygen/xonar_hdmi.c
new file mode 100644
index 0000000..91d92bc
--- /dev/null
+++ b/sound/pci/oxygen/xonar_hdmi.c
@@ -0,0 +1,128 @@
+/*
+ * helper functions for HDMI models (Xonar HDAV1.3/HDAV1.3 Slim)
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/asoundef.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include "xonar.h"
+
+static void hdmi_write_command(struct oxygen *chip, u8 command,
+			       unsigned int count, const u8 *params)
+{
+	unsigned int i;
+	u8 checksum;
+
+	oxygen_write_uart(chip, 0xfb);
+	oxygen_write_uart(chip, 0xef);
+	oxygen_write_uart(chip, command);
+	oxygen_write_uart(chip, count);
+	for (i = 0; i < count; ++i)
+		oxygen_write_uart(chip, params[i]);
+	checksum = 0xfb + 0xef + command + count;
+	for (i = 0; i < count; ++i)
+		checksum += params[i];
+	oxygen_write_uart(chip, checksum);
+}
+
+static void xonar_hdmi_init_commands(struct oxygen *chip,
+				     struct xonar_hdmi *hdmi)
+{
+	u8 param;
+
+	oxygen_reset_uart(chip);
+	param = 0;
+	hdmi_write_command(chip, 0x61, 1, &param);
+	param = 1;
+	hdmi_write_command(chip, 0x74, 1, &param);
+	hdmi_write_command(chip, 0x54, 5, hdmi->params);
+}
+
+void xonar_hdmi_init(struct oxygen *chip, struct xonar_hdmi *hdmi)
+{
+	hdmi->params[1] = IEC958_AES3_CON_FS_48000;
+	hdmi->params[4] = 1;
+	xonar_hdmi_init_commands(chip, hdmi);
+}
+
+void xonar_hdmi_cleanup(struct oxygen *chip)
+{
+	u8 param = 0;
+
+	hdmi_write_command(chip, 0x74, 1, &param);
+}
+
+void xonar_hdmi_resume(struct oxygen *chip, struct xonar_hdmi *hdmi)
+{
+	xonar_hdmi_init_commands(chip, hdmi);
+}
+
+void xonar_hdmi_pcm_hardware_filter(unsigned int channel,
+				    struct snd_pcm_hardware *hardware)
+{
+	if (channel == PCM_MULTICH) {
+		hardware->rates = SNDRV_PCM_RATE_44100 |
+				  SNDRV_PCM_RATE_48000 |
+				  SNDRV_PCM_RATE_96000 |
+				  SNDRV_PCM_RATE_192000;
+		hardware->rate_min = 44100;
+	}
+}
+
+void xonar_set_hdmi_params(struct oxygen *chip, struct xonar_hdmi *hdmi,
+			   struct snd_pcm_hw_params *params)
+{
+	hdmi->params[0] = 0; /* 1 = non-audio */
+	switch (params_rate(params)) {
+	case 44100:
+		hdmi->params[1] = IEC958_AES3_CON_FS_44100;
+		break;
+	case 48000:
+		hdmi->params[1] = IEC958_AES3_CON_FS_48000;
+		break;
+	default: /* 96000 */
+		hdmi->params[1] = IEC958_AES3_CON_FS_96000;
+		break;
+	case 192000:
+		hdmi->params[1] = IEC958_AES3_CON_FS_192000;
+		break;
+	}
+	hdmi->params[2] = params_channels(params) / 2 - 1;
+	if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE)
+		hdmi->params[3] = 0;
+	else
+		hdmi->params[3] = 0xc0;
+	hdmi->params[4] = 1; /* ? */
+	hdmi_write_command(chip, 0x54, 5, hdmi->params);
+}
+
+void xonar_hdmi_uart_input(struct oxygen *chip)
+{
+	if (chip->uart_input_count >= 2 &&
+	    chip->uart_input[chip->uart_input_count - 2] == 'O' &&
+	    chip->uart_input[chip->uart_input_count - 1] == 'K') {
+		dev_dbg(chip->card->dev, "message from HDMI chip received:\n");
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+				     chip->uart_input, chip->uart_input_count);
+		chip->uart_input_count = 0;
+	}
+}
diff --git a/sound/pci/oxygen/xonar_lib.c b/sound/pci/oxygen/xonar_lib.c
new file mode 100644
index 0000000..706b1a4
--- /dev/null
+++ b/sound/pci/oxygen/xonar_lib.c
@@ -0,0 +1,134 @@
+/*
+ * helper functions for Asus Xonar cards
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "xonar.h"
+
+
+#define GPIO_CS53x1_M_MASK	0x000c
+#define GPIO_CS53x1_M_SINGLE	0x0000
+#define GPIO_CS53x1_M_DOUBLE	0x0004
+#define GPIO_CS53x1_M_QUAD	0x0008
+
+
+void xonar_enable_output(struct oxygen *chip)
+{
+	struct xonar_generic *data = chip->model_data;
+
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, data->output_enable_bit);
+	msleep(data->anti_pop_delay);
+	oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, data->output_enable_bit);
+}
+
+void xonar_disable_output(struct oxygen *chip)
+{
+	struct xonar_generic *data = chip->model_data;
+
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, data->output_enable_bit);
+}
+
+static void xonar_ext_power_gpio_changed(struct oxygen *chip)
+{
+	struct xonar_generic *data = chip->model_data;
+	u8 has_power;
+
+	has_power = !!(oxygen_read8(chip, data->ext_power_reg)
+		       & data->ext_power_bit);
+	if (has_power != data->has_power) {
+		data->has_power = has_power;
+		if (has_power) {
+			dev_notice(chip->card->dev, "power restored\n");
+		} else {
+			dev_crit(chip->card->dev,
+				   "Hey! Don't unplug the power cable!\n");
+			/* TODO: stop PCMs */
+		}
+	}
+}
+
+void xonar_init_ext_power(struct oxygen *chip)
+{
+	struct xonar_generic *data = chip->model_data;
+
+	oxygen_set_bits8(chip, data->ext_power_int_reg,
+			 data->ext_power_bit);
+	chip->interrupt_mask |= OXYGEN_INT_GPIO;
+	chip->model.gpio_changed = xonar_ext_power_gpio_changed;
+	data->has_power = !!(oxygen_read8(chip, data->ext_power_reg)
+			     & data->ext_power_bit);
+}
+
+void xonar_init_cs53x1(struct oxygen *chip)
+{
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CS53x1_M_MASK);
+	oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+			      GPIO_CS53x1_M_SINGLE, GPIO_CS53x1_M_MASK);
+}
+
+void xonar_set_cs53x1_params(struct oxygen *chip,
+			     struct snd_pcm_hw_params *params)
+{
+	unsigned int value;
+
+	if (params_rate(params) <= 54000)
+		value = GPIO_CS53x1_M_SINGLE;
+	else if (params_rate(params) <= 108000)
+		value = GPIO_CS53x1_M_DOUBLE;
+	else
+		value = GPIO_CS53x1_M_QUAD;
+	oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+			      value, GPIO_CS53x1_M_MASK);
+}
+
+int xonar_gpio_bit_switch_get(struct snd_kcontrol *ctl,
+			      struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u16 bit = ctl->private_value;
+	bool invert = ctl->private_value & XONAR_GPIO_BIT_INVERT;
+
+	value->value.integer.value[0] =
+		!!(oxygen_read16(chip, OXYGEN_GPIO_DATA) & bit) ^ invert;
+	return 0;
+}
+
+int xonar_gpio_bit_switch_put(struct snd_kcontrol *ctl,
+			      struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u16 bit = ctl->private_value;
+	bool invert = ctl->private_value & XONAR_GPIO_BIT_INVERT;
+	u16 old_bits, new_bits;
+	int changed;
+
+	spin_lock_irq(&chip->reg_lock);
+	old_bits = oxygen_read16(chip, OXYGEN_GPIO_DATA);
+	if (!!value->value.integer.value[0] ^ invert)
+		new_bits = old_bits | bit;
+	else
+		new_bits = old_bits & ~bit;
+	changed = new_bits != old_bits;
+	if (changed)
+		oxygen_write16(chip, OXYGEN_GPIO_DATA, new_bits);
+	spin_unlock_irq(&chip->reg_lock);
+	return changed;
+}
diff --git a/sound/pci/oxygen/xonar_pcm179x.c b/sound/pci/oxygen/xonar_pcm179x.c
new file mode 100644
index 0000000..24109d3
--- /dev/null
+++ b/sound/pci/oxygen/xonar_pcm179x.c
@@ -0,0 +1,1299 @@
+/*
+ * card driver for models with PCM1796 DACs (Xonar D2/D2X/HDAV1.3/ST/STX)
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Xonar D2/D2X
+ * ------------
+ *
+ * CMI8788:
+ *
+ *   SPI 0 -> 1st PCM1796 (front)
+ *   SPI 1 -> 2nd PCM1796 (surround)
+ *   SPI 2 -> 3rd PCM1796 (center/LFE)
+ *   SPI 4 -> 4th PCM1796 (back)
+ *
+ *   GPIO 2 -> M0 of CS5381
+ *   GPIO 3 -> M1 of CS5381
+ *   GPIO 5 <- external power present (D2X only)
+ *   GPIO 7 -> ALT
+ *   GPIO 8 -> enable output to speakers
+ *
+ * CM9780:
+ *
+ *   LINE_OUT -> input of ADC
+ *
+ *   AUX_IN   <- aux
+ *   VIDEO_IN <- CD
+ *   FMIC_IN  <- mic
+ *
+ *   GPO 0 -> route line-in (0) or AC97 output (1) to CS5381 input
+ */
+
+/*
+ * Xonar HDAV1.3 (Deluxe)
+ * ----------------------
+ *
+ * CMI8788:
+ *
+ *   I²C <-> PCM1796 (addr 1001100) (front)
+ *
+ *   GPI 0 <- external power present
+ *
+ *   GPIO 0 -> enable HDMI (0) or speaker (1) output
+ *   GPIO 2 -> M0 of CS5381
+ *   GPIO 3 -> M1 of CS5381
+ *   GPIO 4 <- daughterboard detection
+ *   GPIO 5 <- daughterboard detection
+ *   GPIO 6 -> ?
+ *   GPIO 7 -> ?
+ *   GPIO 8 -> route input jack to line-in (0) or mic-in (1)
+ *
+ *   UART <-> HDMI controller
+ *
+ * CM9780:
+ *
+ *   LINE_OUT -> input of ADC
+ *
+ *   AUX_IN <- aux
+ *   CD_IN  <- CD
+ *   MIC_IN <- mic
+ *
+ *   GPO 0 -> route line-in (0) or AC97 output (1) to CS5381 input
+ *
+ * no daughterboard
+ * ----------------
+ *
+ *   GPIO 4 <- 1
+ *
+ * H6 daughterboard
+ * ----------------
+ *
+ *   GPIO 4 <- 0
+ *   GPIO 5 <- 0
+ *
+ *   I²C <-> PCM1796 (addr 1001101) (surround)
+ *       <-> PCM1796 (addr 1001110) (center/LFE)
+ *       <-> PCM1796 (addr 1001111) (back)
+ *
+ * unknown daughterboard
+ * ---------------------
+ *
+ *   GPIO 4 <- 0
+ *   GPIO 5 <- 1
+ *
+ *   I²C <-> CS4362A (addr 0011000) (surround, center/LFE, back)
+ */
+
+/*
+ * Xonar Essence ST (Deluxe)/STX (II)
+ * ----------------------------------
+ *
+ * CMI8788:
+ *
+ *   I²C <-> PCM1792A (addr 1001100)
+ *       <-> CS2000 (addr 1001110) (ST only)
+ *
+ *   ADC1 MCLK -> REF_CLK of CS2000 (ST only)
+ *
+ *   GPI 0 <- external power present (STX only)
+ *
+ *   GPIO 0 -> enable output to speakers
+ *   GPIO 1 -> route HP to front panel (0) or rear jack (1)
+ *   GPIO 2 -> M0 of CS5381
+ *   GPIO 3 -> M1 of CS5381
+ *   GPIO 4 <- daughterboard detection
+ *   GPIO 5 <- daughterboard detection
+ *   GPIO 6 -> ?
+ *   GPIO 7 -> route output to speaker jacks (0) or HP (1)
+ *   GPIO 8 -> route input jack to line-in (0) or mic-in (1)
+ *
+ * PCM1792A:
+ *
+ *   SCK <- CLK_OUT of CS2000 (ST only)
+ *
+ * CM9780:
+ *
+ *   LINE_OUT -> input of ADC
+ *
+ *   AUX_IN <- aux
+ *   MIC_IN <- mic
+ *
+ *   GPO 0 -> route line-in (0) or AC97 output (1) to CS5381 input
+ *
+ * H6 daughterboard
+ * ----------------
+ *
+ * GPIO 4 <- 0
+ * GPIO 5 <- 0
+ */
+
+/*
+ * Xonar Xense
+ * -----------
+ *
+ * CMI8788:
+ *
+ *   I²C <-> PCM1796 (addr 1001100) (front)
+ *       <-> CS4362A (addr 0011000) (surround, center/LFE, back)
+ *       <-> CS2000 (addr 1001110)
+ *
+ *   ADC1 MCLK -> REF_CLK of CS2000
+ *
+ *   GPI 0 <- external power present
+ *
+ *   GPIO 0 -> enable output
+ *   GPIO 1 -> route HP to front panel (0) or rear jack (1)
+ *   GPIO 2 -> M0 of CS5381
+ *   GPIO 3 -> M1 of CS5381
+ *   GPIO 4 -> enable output
+ *   GPIO 5 -> enable output
+ *   GPIO 6 -> ?
+ *   GPIO 7 -> route output to HP (0) or speaker (1)
+ *   GPIO 8 -> route input jack to mic-in (0) or line-in (1)
+ *
+ * CM9780:
+ *
+ *   LINE_OUT -> input of ADC
+ *
+ *   AUX_IN   <- aux
+ *   VIDEO_IN <- ?
+ *   FMIC_IN  <- mic
+ *
+ *   GPO 0 -> route line-in (0) or AC97 output (1) to CS5381 input
+ *   GPO 1 -> route mic-in from input jack (0) or front panel header (1)
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <sound/ac97_codec.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include "xonar.h"
+#include "cm9780.h"
+#include "pcm1796.h"
+#include "cs2000.h"
+
+
+#define GPIO_D2X_EXT_POWER	0x0020
+#define GPIO_D2_ALT		0x0080
+#define GPIO_D2_OUTPUT_ENABLE	0x0100
+
+#define GPI_EXT_POWER		0x01
+#define GPIO_INPUT_ROUTE	0x0100
+
+#define GPIO_HDAV_OUTPUT_ENABLE	0x0001
+#define GPIO_HDAV_MAGIC		0x00c0
+
+#define GPIO_DB_MASK		0x0030
+#define GPIO_DB_H6		0x0000
+
+#define GPIO_ST_OUTPUT_ENABLE	0x0001
+#define GPIO_ST_HP_REAR		0x0002
+#define GPIO_ST_MAGIC		0x0040
+#define GPIO_ST_HP		0x0080
+
+#define GPIO_XENSE_OUTPUT_ENABLE	(0x0001 | 0x0010 | 0x0020)
+#define GPIO_XENSE_SPEAKERS		0x0080
+
+#define I2C_DEVICE_PCM1796(i)	(0x98 + ((i) << 1))	/* 10011, ii, /W=0 */
+#define I2C_DEVICE_CS2000	0x9c			/* 100111, 0, /W=0 */
+
+#define PCM1796_REG_BASE	16
+
+
+struct xonar_pcm179x {
+	struct xonar_generic generic;
+	unsigned int dacs;
+	u8 pcm1796_regs[4][5];
+	unsigned int current_rate;
+	bool h6;
+	bool hp_active;
+	s8 hp_gain_offset;
+	bool has_cs2000;
+	u8 cs2000_regs[0x1f];
+	bool broken_i2c;
+};
+
+struct xonar_hdav {
+	struct xonar_pcm179x pcm179x;
+	struct xonar_hdmi hdmi;
+};
+
+
+static inline void pcm1796_write_spi(struct oxygen *chip, unsigned int codec,
+				     u8 reg, u8 value)
+{
+	/* maps ALSA channel pair number to SPI output */
+	static const u8 codec_map[4] = {
+		0, 1, 2, 4
+	};
+	oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER  |
+			 OXYGEN_SPI_DATA_LENGTH_2 |
+			 OXYGEN_SPI_CLOCK_160 |
+			 (codec_map[codec] << OXYGEN_SPI_CODEC_SHIFT) |
+			 OXYGEN_SPI_CEN_LATCH_CLOCK_HI,
+			 (reg << 8) | value);
+}
+
+static inline void pcm1796_write_i2c(struct oxygen *chip, unsigned int codec,
+				     u8 reg, u8 value)
+{
+	oxygen_write_i2c(chip, I2C_DEVICE_PCM1796(codec), reg, value);
+}
+
+static void pcm1796_write(struct oxygen *chip, unsigned int codec,
+			  u8 reg, u8 value)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	if ((chip->model.function_flags & OXYGEN_FUNCTION_2WIRE_SPI_MASK) ==
+	    OXYGEN_FUNCTION_SPI)
+		pcm1796_write_spi(chip, codec, reg, value);
+	else
+		pcm1796_write_i2c(chip, codec, reg, value);
+	if ((unsigned int)(reg - PCM1796_REG_BASE)
+	    < ARRAY_SIZE(data->pcm1796_regs[codec]))
+		data->pcm1796_regs[codec][reg - PCM1796_REG_BASE] = value;
+}
+
+static void pcm1796_write_cached(struct oxygen *chip, unsigned int codec,
+				 u8 reg, u8 value)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	if (value != data->pcm1796_regs[codec][reg - PCM1796_REG_BASE])
+		pcm1796_write(chip, codec, reg, value);
+}
+
+static void cs2000_write(struct oxygen *chip, u8 reg, u8 value)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	oxygen_write_i2c(chip, I2C_DEVICE_CS2000, reg, value);
+	data->cs2000_regs[reg] = value;
+}
+
+static void cs2000_write_cached(struct oxygen *chip, u8 reg, u8 value)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	if (value != data->cs2000_regs[reg])
+		cs2000_write(chip, reg, value);
+}
+
+static void pcm1796_registers_init(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+	unsigned int i;
+	s8 gain_offset;
+
+	msleep(1);
+	gain_offset = data->hp_active ? data->hp_gain_offset : 0;
+	for (i = 0; i < data->dacs; ++i) {
+		/* set ATLD before ATL/ATR */
+		pcm1796_write(chip, i, 18,
+			      data->pcm1796_regs[0][18 - PCM1796_REG_BASE]);
+		pcm1796_write(chip, i, 16, chip->dac_volume[i * 2]
+			      + gain_offset);
+		pcm1796_write(chip, i, 17, chip->dac_volume[i * 2 + 1]
+			      + gain_offset);
+		pcm1796_write(chip, i, 19,
+			      data->pcm1796_regs[0][19 - PCM1796_REG_BASE]);
+		pcm1796_write(chip, i, 20,
+			      data->pcm1796_regs[0][20 - PCM1796_REG_BASE]);
+		pcm1796_write(chip, i, 21, 0);
+		gain_offset = 0;
+	}
+}
+
+static void pcm1796_init(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	data->pcm1796_regs[0][18 - PCM1796_REG_BASE] =
+		PCM1796_DMF_DISABLED | PCM1796_FMT_24_I2S | PCM1796_ATLD;
+	if (!data->broken_i2c)
+		data->pcm1796_regs[0][18 - PCM1796_REG_BASE] |= PCM1796_MUTE;
+	data->pcm1796_regs[0][19 - PCM1796_REG_BASE] =
+		PCM1796_FLT_SHARP | PCM1796_ATS_1;
+	data->pcm1796_regs[0][20 - PCM1796_REG_BASE] =
+		data->h6 ? PCM1796_OS_64 : PCM1796_OS_128;
+	pcm1796_registers_init(chip);
+	data->current_rate = 48000;
+}
+
+static void xonar_d2_init(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	data->generic.anti_pop_delay = 300;
+	data->generic.output_enable_bit = GPIO_D2_OUTPUT_ENABLE;
+	data->dacs = 4;
+
+	pcm1796_init(chip);
+
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_D2_ALT);
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_D2_ALT);
+
+	oxygen_ac97_set_bits(chip, 0, CM9780_JACK, CM9780_FMIC2MIC);
+
+	xonar_init_cs53x1(chip);
+	xonar_enable_output(chip);
+
+	snd_component_add(chip->card, "PCM1796");
+	snd_component_add(chip->card, "CS5381");
+}
+
+static void xonar_d2x_init(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	data->generic.ext_power_reg = OXYGEN_GPIO_DATA;
+	data->generic.ext_power_int_reg = OXYGEN_GPIO_INTERRUPT_MASK;
+	data->generic.ext_power_bit = GPIO_D2X_EXT_POWER;
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_D2X_EXT_POWER);
+	xonar_init_ext_power(chip);
+	xonar_d2_init(chip);
+}
+
+static void xonar_hdav_init(struct oxygen *chip)
+{
+	struct xonar_hdav *data = chip->model_data;
+
+	oxygen_write16(chip, OXYGEN_2WIRE_BUS_STATUS,
+		       OXYGEN_2WIRE_LENGTH_8 |
+		       OXYGEN_2WIRE_INTERRUPT_MASK |
+		       OXYGEN_2WIRE_SPEED_STANDARD);
+
+	data->pcm179x.generic.anti_pop_delay = 100;
+	data->pcm179x.generic.output_enable_bit = GPIO_HDAV_OUTPUT_ENABLE;
+	data->pcm179x.generic.ext_power_reg = OXYGEN_GPI_DATA;
+	data->pcm179x.generic.ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK;
+	data->pcm179x.generic.ext_power_bit = GPI_EXT_POWER;
+	data->pcm179x.dacs = chip->model.dac_channels_mixer / 2;
+	data->pcm179x.h6 = chip->model.dac_channels_mixer > 2;
+
+	pcm1796_init(chip);
+
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL,
+			  GPIO_HDAV_MAGIC | GPIO_INPUT_ROUTE);
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_INPUT_ROUTE);
+
+	xonar_init_cs53x1(chip);
+	xonar_init_ext_power(chip);
+	xonar_hdmi_init(chip, &data->hdmi);
+	xonar_enable_output(chip);
+
+	snd_component_add(chip->card, "PCM1796");
+	snd_component_add(chip->card, "CS5381");
+}
+
+static void xonar_st_init_i2c(struct oxygen *chip)
+{
+	oxygen_write16(chip, OXYGEN_2WIRE_BUS_STATUS,
+		       OXYGEN_2WIRE_LENGTH_8 |
+		       OXYGEN_2WIRE_INTERRUPT_MASK |
+		       OXYGEN_2WIRE_SPEED_STANDARD);
+}
+
+static void xonar_st_init_common(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	data->generic.output_enable_bit = GPIO_ST_OUTPUT_ENABLE;
+	data->dacs = chip->model.dac_channels_mixer / 2;
+	data->h6 = chip->model.dac_channels_mixer > 2;
+	data->hp_gain_offset = 2*-18;
+
+	pcm1796_init(chip);
+
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL,
+			  GPIO_INPUT_ROUTE | GPIO_ST_HP_REAR |
+			  GPIO_ST_MAGIC | GPIO_ST_HP);
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA,
+			    GPIO_INPUT_ROUTE | GPIO_ST_HP_REAR | GPIO_ST_HP);
+
+	xonar_init_cs53x1(chip);
+	xonar_enable_output(chip);
+
+	snd_component_add(chip->card, "PCM1792A");
+	snd_component_add(chip->card, "CS5381");
+}
+
+static void cs2000_registers_init(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	cs2000_write(chip, CS2000_GLOBAL_CFG, CS2000_FREEZE);
+	cs2000_write(chip, CS2000_DEV_CTRL, 0);
+	cs2000_write(chip, CS2000_DEV_CFG_1,
+		     CS2000_R_MOD_SEL_1 |
+		     (0 << CS2000_R_SEL_SHIFT) |
+		     CS2000_AUX_OUT_SRC_REF_CLK |
+		     CS2000_EN_DEV_CFG_1);
+	cs2000_write(chip, CS2000_DEV_CFG_2,
+		     (0 << CS2000_LOCK_CLK_SHIFT) |
+		     CS2000_FRAC_N_SRC_STATIC);
+	cs2000_write(chip, CS2000_RATIO_0 + 0, 0x00); /* 1.0 */
+	cs2000_write(chip, CS2000_RATIO_0 + 1, 0x10);
+	cs2000_write(chip, CS2000_RATIO_0 + 2, 0x00);
+	cs2000_write(chip, CS2000_RATIO_0 + 3, 0x00);
+	cs2000_write(chip, CS2000_FUN_CFG_1,
+		     data->cs2000_regs[CS2000_FUN_CFG_1]);
+	cs2000_write(chip, CS2000_FUN_CFG_2, 0);
+	cs2000_write(chip, CS2000_GLOBAL_CFG, CS2000_EN_DEV_CFG_2);
+	msleep(3); /* PLL lock delay */
+}
+
+static void xonar_st_init(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	data->generic.anti_pop_delay = 100;
+	data->h6 = chip->model.dac_channels_mixer > 2;
+	data->has_cs2000 = 1;
+	data->cs2000_regs[CS2000_FUN_CFG_1] = CS2000_REF_CLK_DIV_1;
+	data->broken_i2c = true;
+
+	oxygen_write16(chip, OXYGEN_I2S_A_FORMAT,
+		       OXYGEN_RATE_48000 |
+		       OXYGEN_I2S_FORMAT_I2S |
+		       OXYGEN_I2S_MCLK(data->h6 ? MCLK_256 : MCLK_512) |
+		       OXYGEN_I2S_BITS_16 |
+		       OXYGEN_I2S_MASTER |
+		       OXYGEN_I2S_BCLK_64);
+
+	xonar_st_init_i2c(chip);
+	cs2000_registers_init(chip);
+	xonar_st_init_common(chip);
+
+	snd_component_add(chip->card, "CS2000");
+}
+
+static void xonar_stx_init(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	xonar_st_init_i2c(chip);
+	data->generic.anti_pop_delay = 800;
+	data->generic.ext_power_reg = OXYGEN_GPI_DATA;
+	data->generic.ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK;
+	data->generic.ext_power_bit = GPI_EXT_POWER;
+	xonar_init_ext_power(chip);
+	xonar_st_init_common(chip);
+}
+
+static void xonar_xense_init(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	data->generic.ext_power_reg = OXYGEN_GPI_DATA;
+	data->generic.ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK;
+	data->generic.ext_power_bit = GPI_EXT_POWER;
+	xonar_init_ext_power(chip);
+
+	data->generic.anti_pop_delay = 100;
+	data->has_cs2000 = 1;
+	data->cs2000_regs[CS2000_FUN_CFG_1] = CS2000_REF_CLK_DIV_1;
+
+	oxygen_write16(chip, OXYGEN_I2S_A_FORMAT,
+		OXYGEN_RATE_48000 |
+		OXYGEN_I2S_FORMAT_I2S |
+		OXYGEN_I2S_MCLK(MCLK_512) |
+		OXYGEN_I2S_BITS_16 |
+		OXYGEN_I2S_MASTER |
+		OXYGEN_I2S_BCLK_64);
+
+	xonar_st_init_i2c(chip);
+	cs2000_registers_init(chip);
+
+	data->generic.output_enable_bit = GPIO_XENSE_OUTPUT_ENABLE;
+	data->dacs = 1;
+	data->hp_gain_offset = 2*-18;
+
+	pcm1796_init(chip);
+
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL,
+		GPIO_INPUT_ROUTE | GPIO_ST_HP_REAR |
+		GPIO_ST_MAGIC | GPIO_XENSE_SPEAKERS);
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA,
+		GPIO_INPUT_ROUTE | GPIO_ST_HP_REAR |
+		GPIO_XENSE_SPEAKERS);
+
+	xonar_init_cs53x1(chip);
+	xonar_enable_output(chip);
+
+	snd_component_add(chip->card, "PCM1796");
+	snd_component_add(chip->card, "CS5381");
+	snd_component_add(chip->card, "CS2000");
+}
+
+static void xonar_d2_cleanup(struct oxygen *chip)
+{
+	xonar_disable_output(chip);
+}
+
+static void xonar_hdav_cleanup(struct oxygen *chip)
+{
+	xonar_hdmi_cleanup(chip);
+	xonar_disable_output(chip);
+	msleep(2);
+}
+
+static void xonar_st_cleanup(struct oxygen *chip)
+{
+	xonar_disable_output(chip);
+}
+
+static void xonar_d2_suspend(struct oxygen *chip)
+{
+	xonar_d2_cleanup(chip);
+}
+
+static void xonar_hdav_suspend(struct oxygen *chip)
+{
+	xonar_hdav_cleanup(chip);
+}
+
+static void xonar_st_suspend(struct oxygen *chip)
+{
+	xonar_st_cleanup(chip);
+}
+
+static void xonar_d2_resume(struct oxygen *chip)
+{
+	pcm1796_registers_init(chip);
+	xonar_enable_output(chip);
+}
+
+static void xonar_hdav_resume(struct oxygen *chip)
+{
+	struct xonar_hdav *data = chip->model_data;
+
+	pcm1796_registers_init(chip);
+	xonar_hdmi_resume(chip, &data->hdmi);
+	xonar_enable_output(chip);
+}
+
+static void xonar_stx_resume(struct oxygen *chip)
+{
+	pcm1796_registers_init(chip);
+	xonar_enable_output(chip);
+}
+
+static void xonar_st_resume(struct oxygen *chip)
+{
+	cs2000_registers_init(chip);
+	xonar_stx_resume(chip);
+}
+
+static void update_pcm1796_oversampling(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+	unsigned int i;
+	u8 reg;
+
+	if (data->current_rate <= 48000 && !data->h6)
+		reg = PCM1796_OS_128;
+	else
+		reg = PCM1796_OS_64;
+	for (i = 0; i < data->dacs; ++i)
+		pcm1796_write_cached(chip, i, 20, reg);
+}
+
+static void set_pcm1796_params(struct oxygen *chip,
+			       struct snd_pcm_hw_params *params)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	msleep(1);
+	data->current_rate = params_rate(params);
+	update_pcm1796_oversampling(chip);
+}
+
+static void update_pcm1796_volume(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+	unsigned int i;
+	s8 gain_offset;
+
+	gain_offset = data->hp_active ? data->hp_gain_offset : 0;
+	for (i = 0; i < data->dacs; ++i) {
+		pcm1796_write_cached(chip, i, 16, chip->dac_volume[i * 2]
+				     + gain_offset);
+		pcm1796_write_cached(chip, i, 17, chip->dac_volume[i * 2 + 1]
+				     + gain_offset);
+		gain_offset = 0;
+	}
+}
+
+static void update_pcm1796_mute(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+	unsigned int i;
+	u8 value;
+
+	value = PCM1796_DMF_DISABLED | PCM1796_FMT_24_I2S | PCM1796_ATLD;
+	if (chip->dac_mute)
+		value |= PCM1796_MUTE;
+	for (i = 0; i < data->dacs; ++i)
+		pcm1796_write_cached(chip, i, 18, value);
+}
+
+static void update_cs2000_rate(struct oxygen *chip, unsigned int rate)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+	u8 rate_mclk, reg;
+
+	switch (rate) {
+	case 32000:
+	case 64000:
+		rate_mclk = OXYGEN_RATE_32000;
+		break;
+	case 44100:
+	case 88200:
+	case 176400:
+		rate_mclk = OXYGEN_RATE_44100;
+		break;
+	default:
+	case 48000:
+	case 96000:
+	case 192000:
+		rate_mclk = OXYGEN_RATE_48000;
+		break;
+	}
+
+	if (rate <= 96000 && (rate > 48000 || data->h6)) {
+		rate_mclk |= OXYGEN_I2S_MCLK(MCLK_256);
+		reg = CS2000_REF_CLK_DIV_1;
+	} else {
+		rate_mclk |= OXYGEN_I2S_MCLK(MCLK_512);
+		reg = CS2000_REF_CLK_DIV_2;
+	}
+
+	oxygen_write16_masked(chip, OXYGEN_I2S_A_FORMAT, rate_mclk,
+			      OXYGEN_I2S_RATE_MASK | OXYGEN_I2S_MCLK_MASK);
+	cs2000_write_cached(chip, CS2000_FUN_CFG_1, reg);
+	msleep(3); /* PLL lock delay */
+}
+
+static void set_st_params(struct oxygen *chip,
+			  struct snd_pcm_hw_params *params)
+{
+	update_cs2000_rate(chip, params_rate(params));
+	set_pcm1796_params(chip, params);
+}
+
+static void set_hdav_params(struct oxygen *chip,
+			    struct snd_pcm_hw_params *params)
+{
+	struct xonar_hdav *data = chip->model_data;
+
+	set_pcm1796_params(chip, params);
+	xonar_set_hdmi_params(chip, &data->hdmi, params);
+}
+
+static const struct snd_kcontrol_new alt_switch = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Analog Loopback Switch",
+	.info = snd_ctl_boolean_mono_info,
+	.get = xonar_gpio_bit_switch_get,
+	.put = xonar_gpio_bit_switch_put,
+	.private_value = GPIO_D2_ALT,
+};
+
+static int rolloff_info(struct snd_kcontrol *ctl,
+			struct snd_ctl_elem_info *info)
+{
+	static const char *const names[2] = {
+		"Sharp Roll-off", "Slow Roll-off"
+	};
+
+	return snd_ctl_enum_info(info, 1, 2, names);
+}
+
+static int rolloff_get(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_pcm179x *data = chip->model_data;
+
+	value->value.enumerated.item[0] =
+		(data->pcm1796_regs[0][19 - PCM1796_REG_BASE] &
+		 PCM1796_FLT_MASK) != PCM1796_FLT_SHARP;
+	return 0;
+}
+
+static int rolloff_put(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_pcm179x *data = chip->model_data;
+	unsigned int i;
+	int changed;
+	u8 reg;
+
+	mutex_lock(&chip->mutex);
+	reg = data->pcm1796_regs[0][19 - PCM1796_REG_BASE];
+	reg &= ~PCM1796_FLT_MASK;
+	if (!value->value.enumerated.item[0])
+		reg |= PCM1796_FLT_SHARP;
+	else
+		reg |= PCM1796_FLT_SLOW;
+	changed = reg != data->pcm1796_regs[0][19 - PCM1796_REG_BASE];
+	if (changed) {
+		for (i = 0; i < data->dacs; ++i)
+			pcm1796_write(chip, i, 19, reg);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static const struct snd_kcontrol_new rolloff_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "DAC Filter Playback Enum",
+	.info = rolloff_info,
+	.get = rolloff_get,
+	.put = rolloff_put,
+};
+
+static const struct snd_kcontrol_new hdav_hdmi_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "HDMI Playback Switch",
+	.info = snd_ctl_boolean_mono_info,
+	.get = xonar_gpio_bit_switch_get,
+	.put = xonar_gpio_bit_switch_put,
+	.private_value = GPIO_HDAV_OUTPUT_ENABLE | XONAR_GPIO_BIT_INVERT,
+};
+
+static int st_output_switch_info(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_info *info)
+{
+	static const char *const names[3] = {
+		"Speakers", "Headphones", "FP Headphones"
+	};
+
+	return snd_ctl_enum_info(info, 1, 3, names);
+}
+
+static int st_output_switch_get(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u16 gpio;
+
+	gpio = oxygen_read16(chip, OXYGEN_GPIO_DATA);
+	if (!(gpio & GPIO_ST_HP))
+		value->value.enumerated.item[0] = 0;
+	else if (gpio & GPIO_ST_HP_REAR)
+		value->value.enumerated.item[0] = 1;
+	else
+		value->value.enumerated.item[0] = 2;
+	return 0;
+}
+
+
+static int st_output_switch_put(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_pcm179x *data = chip->model_data;
+	u16 gpio_old, gpio;
+
+	mutex_lock(&chip->mutex);
+	gpio_old = oxygen_read16(chip, OXYGEN_GPIO_DATA);
+	gpio = gpio_old;
+	switch (value->value.enumerated.item[0]) {
+	case 0:
+		gpio &= ~(GPIO_ST_HP | GPIO_ST_HP_REAR);
+		break;
+	case 1:
+		gpio |= GPIO_ST_HP | GPIO_ST_HP_REAR;
+		break;
+	case 2:
+		gpio = (gpio | GPIO_ST_HP) & ~GPIO_ST_HP_REAR;
+		break;
+	}
+	oxygen_write16(chip, OXYGEN_GPIO_DATA, gpio);
+	data->hp_active = gpio & GPIO_ST_HP;
+	update_pcm1796_volume(chip);
+	mutex_unlock(&chip->mutex);
+	return gpio != gpio_old;
+}
+
+static int st_hp_volume_offset_info(struct snd_kcontrol *ctl,
+				    struct snd_ctl_elem_info *info)
+{
+	static const char *const names[4] = {
+		"< 32 ohms", "32-64 ohms", "64-300 ohms", "300-600 ohms"
+	};
+
+	return snd_ctl_enum_info(info, 1, 4, names);
+}
+
+static int st_hp_volume_offset_get(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_pcm179x *data = chip->model_data;
+
+	mutex_lock(&chip->mutex);
+	if (data->hp_gain_offset < 2*-12)
+		value->value.enumerated.item[0] = 0;
+	else if (data->hp_gain_offset < 2*-6)
+		value->value.enumerated.item[0] = 1;
+	else if (data->hp_gain_offset < 0)
+		value->value.enumerated.item[0] = 2;
+	else
+		value->value.enumerated.item[0] = 3;
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+
+static int st_hp_volume_offset_put(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_value *value)
+{
+	static const s8 offsets[] = { 2*-18, 2*-12, 2*-6, 0 };
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_pcm179x *data = chip->model_data;
+	s8 offset;
+	int changed;
+
+	if (value->value.enumerated.item[0] > 3)
+		return -EINVAL;
+	offset = offsets[value->value.enumerated.item[0]];
+	mutex_lock(&chip->mutex);
+	changed = offset != data->hp_gain_offset;
+	if (changed) {
+		data->hp_gain_offset = offset;
+		update_pcm1796_volume(chip);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static const struct snd_kcontrol_new st_controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Output",
+		.info = st_output_switch_info,
+		.get = st_output_switch_get,
+		.put = st_output_switch_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Headphones Impedance Playback Enum",
+		.info = st_hp_volume_offset_info,
+		.get = st_hp_volume_offset_get,
+		.put = st_hp_volume_offset_put,
+	},
+};
+
+static int xense_output_switch_get(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u16 gpio;
+
+	gpio = oxygen_read16(chip, OXYGEN_GPIO_DATA);
+	if (gpio & GPIO_XENSE_SPEAKERS)
+		value->value.enumerated.item[0] = 0;
+	else if (!(gpio & GPIO_XENSE_SPEAKERS) && (gpio & GPIO_ST_HP_REAR))
+		value->value.enumerated.item[0] = 1;
+	else
+		value->value.enumerated.item[0] = 2;
+	return 0;
+}
+
+static int xense_output_switch_put(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_pcm179x *data = chip->model_data;
+	u16 gpio_old, gpio;
+
+	mutex_lock(&chip->mutex);
+	gpio_old = oxygen_read16(chip, OXYGEN_GPIO_DATA);
+	gpio = gpio_old;
+	switch (value->value.enumerated.item[0]) {
+	case 0:
+		gpio |= GPIO_XENSE_SPEAKERS | GPIO_ST_HP_REAR;
+		break;
+	case 1:
+		gpio = (gpio | GPIO_ST_HP_REAR) & ~GPIO_XENSE_SPEAKERS;
+		break;
+	case 2:
+		gpio &= ~(GPIO_XENSE_SPEAKERS | GPIO_ST_HP_REAR);
+		break;
+	}
+	oxygen_write16(chip, OXYGEN_GPIO_DATA, gpio);
+	data->hp_active = !(gpio & GPIO_XENSE_SPEAKERS);
+	update_pcm1796_volume(chip);
+	mutex_unlock(&chip->mutex);
+	return gpio != gpio_old;
+}
+
+static const struct snd_kcontrol_new xense_controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Output",
+		.info = st_output_switch_info,
+		.get = xense_output_switch_get,
+		.put = xense_output_switch_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Headphones Impedance Playback Enum",
+		.info = st_hp_volume_offset_info,
+		.get = st_hp_volume_offset_get,
+		.put = st_hp_volume_offset_put,
+	},
+};
+
+static void xonar_line_mic_ac97_switch(struct oxygen *chip,
+				       unsigned int reg, unsigned int mute)
+{
+	if (reg == AC97_LINE) {
+		spin_lock_irq(&chip->reg_lock);
+		oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+				      mute ? GPIO_INPUT_ROUTE : 0,
+				      GPIO_INPUT_ROUTE);
+		spin_unlock_irq(&chip->reg_lock);
+	}
+}
+
+static const DECLARE_TLV_DB_SCALE(pcm1796_db_scale, -6000, 50, 0);
+
+static int xonar_d2_control_filter(struct snd_kcontrol_new *template)
+{
+	if (!strncmp(template->name, "CD Capture ", 11))
+		/* CD in is actually connected to the video in pin */
+		template->private_value ^= AC97_CD ^ AC97_VIDEO;
+	return 0;
+}
+
+static int xonar_st_h6_control_filter(struct snd_kcontrol_new *template)
+{
+	if (!strncmp(template->name, "Master Playback ", 16))
+		/* no volume/mute, as I²C to the third DAC does not work */
+		return 1;
+	return 0;
+}
+
+static int add_pcm1796_controls(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+	int err;
+
+	if (!data->broken_i2c) {
+		err = snd_ctl_add(chip->card,
+				  snd_ctl_new1(&rolloff_control, chip));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int xonar_d2_mixer_init(struct oxygen *chip)
+{
+	int err;
+
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&alt_switch, chip));
+	if (err < 0)
+		return err;
+	err = add_pcm1796_controls(chip);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int xonar_hdav_mixer_init(struct oxygen *chip)
+{
+	int err;
+
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&hdav_hdmi_control, chip));
+	if (err < 0)
+		return err;
+	err = add_pcm1796_controls(chip);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int xonar_st_mixer_init(struct oxygen *chip)
+{
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < ARRAY_SIZE(st_controls); ++i) {
+		err = snd_ctl_add(chip->card,
+				  snd_ctl_new1(&st_controls[i], chip));
+		if (err < 0)
+			return err;
+	}
+	err = add_pcm1796_controls(chip);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int xonar_xense_mixer_init(struct oxygen *chip)
+{
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < ARRAY_SIZE(xense_controls); ++i) {
+		err = snd_ctl_add(chip->card,
+		snd_ctl_new1(&xense_controls[i], chip));
+		if (err < 0)
+			return err;
+	}
+	err = add_pcm1796_controls(chip);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static void dump_pcm1796_registers(struct oxygen *chip,
+				   struct snd_info_buffer *buffer)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+	unsigned int dac, i;
+
+	for (dac = 0; dac < data->dacs; ++dac) {
+		snd_iprintf(buffer, "\nPCM1796 %u:", dac + 1);
+		for (i = 0; i < 5; ++i)
+			snd_iprintf(buffer, " %02x",
+				    data->pcm1796_regs[dac][i]);
+	}
+	snd_iprintf(buffer, "\n");
+}
+
+static void dump_cs2000_registers(struct oxygen *chip,
+				  struct snd_info_buffer *buffer)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+	unsigned int i;
+
+	if (data->has_cs2000) {
+		snd_iprintf(buffer, "\nCS2000:\n00:   ");
+		for (i = 1; i < 0x10; ++i)
+			snd_iprintf(buffer, " %02x", data->cs2000_regs[i]);
+		snd_iprintf(buffer, "\n10:");
+		for (i = 0x10; i < 0x1f; ++i)
+			snd_iprintf(buffer, " %02x", data->cs2000_regs[i]);
+		snd_iprintf(buffer, "\n");
+	}
+}
+
+static void dump_st_registers(struct oxygen *chip,
+			      struct snd_info_buffer *buffer)
+{
+	dump_pcm1796_registers(chip, buffer);
+	dump_cs2000_registers(chip, buffer);
+}
+
+static const struct oxygen_model model_xonar_d2 = {
+	.longname = "Asus Virtuoso 200",
+	.chip = "AV200",
+	.init = xonar_d2_init,
+	.control_filter = xonar_d2_control_filter,
+	.mixer_init = xonar_d2_mixer_init,
+	.cleanup = xonar_d2_cleanup,
+	.suspend = xonar_d2_suspend,
+	.resume = xonar_d2_resume,
+	.set_dac_params = set_pcm1796_params,
+	.set_adc_params = xonar_set_cs53x1_params,
+	.update_dac_volume = update_pcm1796_volume,
+	.update_dac_mute = update_pcm1796_mute,
+	.dump_registers = dump_pcm1796_registers,
+	.dac_tlv = pcm1796_db_scale,
+	.model_data_size = sizeof(struct xonar_pcm179x),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 CAPTURE_0_FROM_I2S_2 |
+			 CAPTURE_1_FROM_SPDIF |
+			 MIDI_OUTPUT |
+			 MIDI_INPUT |
+			 AC97_CD_INPUT,
+	.dac_channels_pcm = 8,
+	.dac_channels_mixer = 8,
+	.dac_volume_min = 255 - 2*60,
+	.dac_volume_max = 255,
+	.misc_flags = OXYGEN_MISC_MIDI,
+	.function_flags = OXYGEN_FUNCTION_SPI |
+			  OXYGEN_FUNCTION_ENABLE_SPI_4_5,
+	.dac_mclks = OXYGEN_MCLKS(512, 128, 128),
+	.adc_mclks = OXYGEN_MCLKS(256, 128, 128),
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_I2S,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
+
+static const struct oxygen_model model_xonar_hdav = {
+	.longname = "Asus Virtuoso 200",
+	.chip = "AV200",
+	.init = xonar_hdav_init,
+	.mixer_init = xonar_hdav_mixer_init,
+	.cleanup = xonar_hdav_cleanup,
+	.suspend = xonar_hdav_suspend,
+	.resume = xonar_hdav_resume,
+	.pcm_hardware_filter = xonar_hdmi_pcm_hardware_filter,
+	.set_dac_params = set_hdav_params,
+	.set_adc_params = xonar_set_cs53x1_params,
+	.update_dac_volume = update_pcm1796_volume,
+	.update_dac_mute = update_pcm1796_mute,
+	.uart_input = xonar_hdmi_uart_input,
+	.ac97_switch = xonar_line_mic_ac97_switch,
+	.dump_registers = dump_pcm1796_registers,
+	.dac_tlv = pcm1796_db_scale,
+	.model_data_size = sizeof(struct xonar_hdav),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 CAPTURE_0_FROM_I2S_2 |
+			 CAPTURE_1_FROM_SPDIF,
+	.dac_channels_pcm = 8,
+	.dac_channels_mixer = 2,
+	.dac_volume_min = 255 - 2*60,
+	.dac_volume_max = 255,
+	.misc_flags = OXYGEN_MISC_MIDI,
+	.function_flags = OXYGEN_FUNCTION_2WIRE,
+	.dac_mclks = OXYGEN_MCLKS(512, 128, 128),
+	.adc_mclks = OXYGEN_MCLKS(256, 128, 128),
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_I2S,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
+
+static const struct oxygen_model model_xonar_st = {
+	.longname = "Asus Virtuoso 100",
+	.chip = "AV200",
+	.init = xonar_st_init,
+	.mixer_init = xonar_st_mixer_init,
+	.cleanup = xonar_st_cleanup,
+	.suspend = xonar_st_suspend,
+	.resume = xonar_st_resume,
+	.set_dac_params = set_st_params,
+	.set_adc_params = xonar_set_cs53x1_params,
+	.update_dac_volume = update_pcm1796_volume,
+	.update_dac_mute = update_pcm1796_mute,
+	.ac97_switch = xonar_line_mic_ac97_switch,
+	.dump_registers = dump_st_registers,
+	.dac_tlv = pcm1796_db_scale,
+	.model_data_size = sizeof(struct xonar_pcm179x),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 CAPTURE_0_FROM_I2S_2 |
+			 CAPTURE_1_FROM_SPDIF |
+			 AC97_FMIC_SWITCH,
+	.dac_channels_pcm = 2,
+	.dac_channels_mixer = 2,
+	.dac_volume_min = 255 - 2*60,
+	.dac_volume_max = 255,
+	.function_flags = OXYGEN_FUNCTION_2WIRE,
+	.dac_mclks = OXYGEN_MCLKS(512, 128, 128),
+	.adc_mclks = OXYGEN_MCLKS(256, 128, 128),
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_I2S,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
+
+int get_xonar_pcm179x_model(struct oxygen *chip,
+			    const struct pci_device_id *id)
+{
+	switch (id->subdevice) {
+	case 0x8269:
+		chip->model = model_xonar_d2;
+		chip->model.shortname = "Xonar D2";
+		break;
+	case 0x82b7:
+		chip->model = model_xonar_d2;
+		chip->model.shortname = "Xonar D2X";
+		chip->model.init = xonar_d2x_init;
+		break;
+	case 0x8314:
+		chip->model = model_xonar_hdav;
+		oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_DB_MASK);
+		switch (oxygen_read16(chip, OXYGEN_GPIO_DATA) & GPIO_DB_MASK) {
+		default:
+			chip->model.shortname = "Xonar HDAV1.3";
+			break;
+		case GPIO_DB_H6:
+			chip->model.shortname = "Xonar HDAV1.3+H6";
+			chip->model.dac_channels_mixer = 8;
+			chip->model.dac_mclks = OXYGEN_MCLKS(256, 128, 128);
+			break;
+		}
+		break;
+	case 0x835d:
+		chip->model = model_xonar_st;
+		oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_DB_MASK);
+		switch (oxygen_read16(chip, OXYGEN_GPIO_DATA) & GPIO_DB_MASK) {
+		default:
+			chip->model.shortname = "Xonar ST";
+			break;
+		case GPIO_DB_H6:
+			chip->model.shortname = "Xonar ST+H6";
+			chip->model.control_filter = xonar_st_h6_control_filter;
+			chip->model.dac_channels_pcm = 8;
+			chip->model.dac_channels_mixer = 8;
+			chip->model.dac_volume_min = 255;
+			chip->model.dac_mclks = OXYGEN_MCLKS(256, 128, 128);
+			break;
+		}
+		break;
+	case 0x835c:
+		chip->model = model_xonar_st;
+		chip->model.shortname = "Xonar STX";
+		chip->model.init = xonar_stx_init;
+		chip->model.resume = xonar_stx_resume;
+		chip->model.set_dac_params = set_pcm1796_params;
+		break;
+	case 0x85f4:
+		chip->model = model_xonar_st;
+		oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_DB_MASK);
+		switch (oxygen_read16(chip, OXYGEN_GPIO_DATA) & GPIO_DB_MASK) {
+		default:
+			chip->model.shortname = "Xonar STX II";
+			break;
+		case GPIO_DB_H6:
+			chip->model.shortname = "Xonar STX II+H6";
+			chip->model.dac_channels_pcm = 8;
+			chip->model.dac_channels_mixer = 8;
+			chip->model.dac_mclks = OXYGEN_MCLKS(256, 128, 128);
+			break;
+		}
+		chip->model.init = xonar_stx_init;
+		chip->model.resume = xonar_stx_resume;
+		chip->model.set_dac_params = set_pcm1796_params;
+		break;
+	case 0x8428:
+		chip->model = model_xonar_st;
+		chip->model.shortname = "Xonar Xense";
+		chip->model.chip = "AV100";
+		chip->model.init = xonar_xense_init;
+		chip->model.mixer_init = xonar_xense_mixer_init;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
diff --git a/sound/pci/oxygen/xonar_wm87x6.c b/sound/pci/oxygen/xonar_wm87x6.c
new file mode 100644
index 0000000..90ac479
--- /dev/null
+++ b/sound/pci/oxygen/xonar_wm87x6.c
@@ -0,0 +1,1342 @@
+/*
+ * card driver for models with WM8776/WM8766 DACs (Xonar DS/HDAV1.3 Slim)
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Xonar DS
+ * --------
+ *
+ * CMI8788:
+ *
+ *   SPI 0 -> WM8766 (surround, center/LFE, back)
+ *   SPI 1 -> WM8776 (front, input)
+ *
+ *   GPIO 4 <- headphone detect, 0 = plugged
+ *   GPIO 6 -> route input jack to mic-in (0) or line-in (1)
+ *   GPIO 7 -> enable output to front L/R speaker channels
+ *   GPIO 8 -> enable output to other speaker channels and front panel headphone
+ *
+ * WM8776:
+ *
+ *   input 1 <- line
+ *   input 2 <- mic
+ *   input 3 <- front mic
+ *   input 4 <- aux
+ */
+
+/*
+ * Xonar HDAV1.3 Slim
+ * ------------------
+ *
+ * CMI8788:
+ *
+ *   I²C <-> WM8776 (addr 0011010)
+ *
+ *   GPIO 0  -> disable HDMI output
+ *   GPIO 1  -> enable HP output
+ *   GPIO 6  -> firmware EEPROM I²C clock
+ *   GPIO 7 <-> firmware EEPROM I²C data
+ *
+ *   UART <-> HDMI controller
+ *
+ * WM8776:
+ *
+ *   input 1 <- mic
+ *   input 2 <- aux
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include "xonar.h"
+#include "wm8776.h"
+#include "wm8766.h"
+
+#define GPIO_DS_HP_DETECT	0x0010
+#define GPIO_DS_INPUT_ROUTE	0x0040
+#define GPIO_DS_OUTPUT_FRONTLR	0x0080
+#define GPIO_DS_OUTPUT_ENABLE	0x0100
+
+#define GPIO_SLIM_HDMI_DISABLE	0x0001
+#define GPIO_SLIM_OUTPUT_ENABLE	0x0002
+#define GPIO_SLIM_FIRMWARE_CLK	0x0040
+#define GPIO_SLIM_FIRMWARE_DATA	0x0080
+
+#define I2C_DEVICE_WM8776	0x34	/* 001101, 0, /W=0 */
+
+#define LC_CONTROL_LIMITER	0x40000000
+#define LC_CONTROL_ALC		0x20000000
+
+struct xonar_wm87x6 {
+	struct xonar_generic generic;
+	u16 wm8776_regs[0x17];
+	u16 wm8766_regs[0x10];
+	struct snd_kcontrol *line_adcmux_control;
+	struct snd_kcontrol *mic_adcmux_control;
+	struct snd_kcontrol *lc_controls[13];
+	struct snd_jack *hp_jack;
+	struct xonar_hdmi hdmi;
+};
+
+static void wm8776_write_spi(struct oxygen *chip,
+			     unsigned int reg, unsigned int value)
+{
+	oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+			 OXYGEN_SPI_DATA_LENGTH_2 |
+			 OXYGEN_SPI_CLOCK_160 |
+			 (1 << OXYGEN_SPI_CODEC_SHIFT) |
+			 OXYGEN_SPI_CEN_LATCH_CLOCK_LO,
+			 (reg << 9) | value);
+}
+
+static void wm8776_write_i2c(struct oxygen *chip,
+			     unsigned int reg, unsigned int value)
+{
+	oxygen_write_i2c(chip, I2C_DEVICE_WM8776,
+			 (reg << 1) | (value >> 8), value);
+}
+
+static void wm8776_write(struct oxygen *chip,
+			 unsigned int reg, unsigned int value)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	if ((chip->model.function_flags & OXYGEN_FUNCTION_2WIRE_SPI_MASK) ==
+	    OXYGEN_FUNCTION_SPI)
+		wm8776_write_spi(chip, reg, value);
+	else
+		wm8776_write_i2c(chip, reg, value);
+	if (reg < ARRAY_SIZE(data->wm8776_regs)) {
+		if (reg >= WM8776_HPLVOL && reg <= WM8776_DACMASTER)
+			value &= ~WM8776_UPDATE;
+		data->wm8776_regs[reg] = value;
+	}
+}
+
+static void wm8776_write_cached(struct oxygen *chip,
+				unsigned int reg, unsigned int value)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	if (reg >= ARRAY_SIZE(data->wm8776_regs) ||
+	    value != data->wm8776_regs[reg])
+		wm8776_write(chip, reg, value);
+}
+
+static void wm8766_write(struct oxygen *chip,
+			 unsigned int reg, unsigned int value)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+			 OXYGEN_SPI_DATA_LENGTH_2 |
+			 OXYGEN_SPI_CLOCK_160 |
+			 (0 << OXYGEN_SPI_CODEC_SHIFT) |
+			 OXYGEN_SPI_CEN_LATCH_CLOCK_LO,
+			 (reg << 9) | value);
+	if (reg < ARRAY_SIZE(data->wm8766_regs)) {
+		if ((reg >= WM8766_LDA1 && reg <= WM8766_RDA1) ||
+		    (reg >= WM8766_LDA2 && reg <= WM8766_MASTDA))
+			value &= ~WM8766_UPDATE;
+		data->wm8766_regs[reg] = value;
+	}
+}
+
+static void wm8766_write_cached(struct oxygen *chip,
+				unsigned int reg, unsigned int value)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	if (reg >= ARRAY_SIZE(data->wm8766_regs) ||
+	    value != data->wm8766_regs[reg])
+		wm8766_write(chip, reg, value);
+}
+
+static void wm8776_registers_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	wm8776_write(chip, WM8776_RESET, 0);
+	wm8776_write(chip, WM8776_PHASESWAP, WM8776_PH_MASK);
+	wm8776_write(chip, WM8776_DACCTRL1, WM8776_DZCEN |
+		     WM8776_PL_LEFT_LEFT | WM8776_PL_RIGHT_RIGHT);
+	wm8776_write(chip, WM8776_DACMUTE, chip->dac_mute ? WM8776_DMUTE : 0);
+	wm8776_write(chip, WM8776_DACIFCTRL,
+		     WM8776_DACFMT_LJUST | WM8776_DACWL_24);
+	wm8776_write(chip, WM8776_ADCIFCTRL,
+		     data->wm8776_regs[WM8776_ADCIFCTRL]);
+	wm8776_write(chip, WM8776_MSTRCTRL, data->wm8776_regs[WM8776_MSTRCTRL]);
+	wm8776_write(chip, WM8776_PWRDOWN, data->wm8776_regs[WM8776_PWRDOWN]);
+	wm8776_write(chip, WM8776_HPLVOL, data->wm8776_regs[WM8776_HPLVOL]);
+	wm8776_write(chip, WM8776_HPRVOL, data->wm8776_regs[WM8776_HPRVOL] |
+		     WM8776_UPDATE);
+	wm8776_write(chip, WM8776_ADCLVOL, data->wm8776_regs[WM8776_ADCLVOL]);
+	wm8776_write(chip, WM8776_ADCRVOL, data->wm8776_regs[WM8776_ADCRVOL]);
+	wm8776_write(chip, WM8776_ADCMUX, data->wm8776_regs[WM8776_ADCMUX]);
+	wm8776_write(chip, WM8776_DACLVOL, chip->dac_volume[0]);
+	wm8776_write(chip, WM8776_DACRVOL, chip->dac_volume[1] | WM8776_UPDATE);
+}
+
+static void wm8766_registers_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	wm8766_write(chip, WM8766_RESET, 0);
+	wm8766_write(chip, WM8766_DAC_CTRL, data->wm8766_regs[WM8766_DAC_CTRL]);
+	wm8766_write(chip, WM8766_INT_CTRL, WM8766_FMT_LJUST | WM8766_IWL_24);
+	wm8766_write(chip, WM8766_DAC_CTRL2,
+		     WM8766_ZCD | (chip->dac_mute ? WM8766_DMUTE_MASK : 0));
+	wm8766_write(chip, WM8766_LDA1, chip->dac_volume[2]);
+	wm8766_write(chip, WM8766_RDA1, chip->dac_volume[3]);
+	wm8766_write(chip, WM8766_LDA2, chip->dac_volume[4]);
+	wm8766_write(chip, WM8766_RDA2, chip->dac_volume[5]);
+	wm8766_write(chip, WM8766_LDA3, chip->dac_volume[6]);
+	wm8766_write(chip, WM8766_RDA3, chip->dac_volume[7] | WM8766_UPDATE);
+}
+
+static void wm8776_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	data->wm8776_regs[WM8776_HPLVOL] = (0x79 - 60) | WM8776_HPZCEN;
+	data->wm8776_regs[WM8776_HPRVOL] = (0x79 - 60) | WM8776_HPZCEN;
+	data->wm8776_regs[WM8776_ADCIFCTRL] =
+		WM8776_ADCFMT_LJUST | WM8776_ADCWL_24 | WM8776_ADCMCLK;
+	data->wm8776_regs[WM8776_MSTRCTRL] =
+		WM8776_ADCRATE_256 | WM8776_DACRATE_256;
+	data->wm8776_regs[WM8776_PWRDOWN] = WM8776_HPPD;
+	data->wm8776_regs[WM8776_ADCLVOL] = 0xa5 | WM8776_ZCA;
+	data->wm8776_regs[WM8776_ADCRVOL] = 0xa5 | WM8776_ZCA;
+	data->wm8776_regs[WM8776_ADCMUX] = 0x001;
+	wm8776_registers_init(chip);
+}
+
+static void wm8766_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	data->wm8766_regs[WM8766_DAC_CTRL] =
+		WM8766_PL_LEFT_LEFT | WM8766_PL_RIGHT_RIGHT;
+	wm8766_registers_init(chip);
+}
+
+static void xonar_ds_handle_hp_jack(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+	bool hp_plugged;
+	unsigned int reg;
+
+	mutex_lock(&chip->mutex);
+
+	hp_plugged = !(oxygen_read16(chip, OXYGEN_GPIO_DATA) &
+		       GPIO_DS_HP_DETECT);
+
+	oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+			      hp_plugged ? 0 : GPIO_DS_OUTPUT_FRONTLR,
+			      GPIO_DS_OUTPUT_FRONTLR);
+
+	reg = data->wm8766_regs[WM8766_DAC_CTRL] & ~WM8766_MUTEALL;
+	if (hp_plugged)
+		reg |= WM8766_MUTEALL;
+	wm8766_write_cached(chip, WM8766_DAC_CTRL, reg);
+
+	snd_jack_report(data->hp_jack, hp_plugged ? SND_JACK_HEADPHONE : 0);
+
+	mutex_unlock(&chip->mutex);
+}
+
+static void xonar_ds_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	data->generic.anti_pop_delay = 300;
+	data->generic.output_enable_bit = GPIO_DS_OUTPUT_ENABLE;
+
+	wm8776_init(chip);
+	wm8766_init(chip);
+
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL,
+			  GPIO_DS_INPUT_ROUTE | GPIO_DS_OUTPUT_FRONTLR);
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL,
+			    GPIO_DS_HP_DETECT);
+	oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_DS_INPUT_ROUTE);
+	oxygen_set_bits16(chip, OXYGEN_GPIO_INTERRUPT_MASK, GPIO_DS_HP_DETECT);
+	chip->interrupt_mask |= OXYGEN_INT_GPIO;
+
+	xonar_enable_output(chip);
+
+	snd_jack_new(chip->card, "Headphone",
+		     SND_JACK_HEADPHONE, &data->hp_jack, false, false);
+	xonar_ds_handle_hp_jack(chip);
+
+	snd_component_add(chip->card, "WM8776");
+	snd_component_add(chip->card, "WM8766");
+}
+
+static void xonar_hdav_slim_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	data->generic.anti_pop_delay = 300;
+	data->generic.output_enable_bit = GPIO_SLIM_OUTPUT_ENABLE;
+
+	wm8776_init(chip);
+
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL,
+			  GPIO_SLIM_HDMI_DISABLE |
+			  GPIO_SLIM_FIRMWARE_CLK |
+			  GPIO_SLIM_FIRMWARE_DATA);
+
+	xonar_hdmi_init(chip, &data->hdmi);
+	xonar_enable_output(chip);
+
+	snd_component_add(chip->card, "WM8776");
+}
+
+static void xonar_ds_cleanup(struct oxygen *chip)
+{
+	xonar_disable_output(chip);
+	wm8776_write(chip, WM8776_RESET, 0);
+}
+
+static void xonar_hdav_slim_cleanup(struct oxygen *chip)
+{
+	xonar_hdmi_cleanup(chip);
+	xonar_disable_output(chip);
+	wm8776_write(chip, WM8776_RESET, 0);
+	msleep(2);
+}
+
+static void xonar_ds_suspend(struct oxygen *chip)
+{
+	xonar_ds_cleanup(chip);
+}
+
+static void xonar_hdav_slim_suspend(struct oxygen *chip)
+{
+	xonar_hdav_slim_cleanup(chip);
+}
+
+static void xonar_ds_resume(struct oxygen *chip)
+{
+	wm8776_registers_init(chip);
+	wm8766_registers_init(chip);
+	xonar_enable_output(chip);
+	xonar_ds_handle_hp_jack(chip);
+}
+
+static void xonar_hdav_slim_resume(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	wm8776_registers_init(chip);
+	xonar_hdmi_resume(chip, &data->hdmi);
+	xonar_enable_output(chip);
+}
+
+static void wm8776_adc_hardware_filter(unsigned int channel,
+				       struct snd_pcm_hardware *hardware)
+{
+	if (channel == PCM_A) {
+		hardware->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;
+		hardware->rate_max = 96000;
+	}
+}
+
+static void xonar_hdav_slim_hardware_filter(unsigned int channel,
+					    struct snd_pcm_hardware *hardware)
+{
+	wm8776_adc_hardware_filter(channel, hardware);
+	xonar_hdmi_pcm_hardware_filter(channel, hardware);
+}
+
+static void set_wm87x6_dac_params(struct oxygen *chip,
+				  struct snd_pcm_hw_params *params)
+{
+}
+
+static void set_wm8776_adc_params(struct oxygen *chip,
+				  struct snd_pcm_hw_params *params)
+{
+	u16 reg;
+
+	reg = WM8776_ADCRATE_256 | WM8776_DACRATE_256;
+	if (params_rate(params) > 48000)
+		reg |= WM8776_ADCOSR;
+	wm8776_write_cached(chip, WM8776_MSTRCTRL, reg);
+}
+
+static void set_hdav_slim_dac_params(struct oxygen *chip,
+				     struct snd_pcm_hw_params *params)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	xonar_set_hdmi_params(chip, &data->hdmi, params);
+}
+
+static void update_wm8776_volume(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+	u8 to_change;
+
+	if (chip->dac_volume[0] == chip->dac_volume[1]) {
+		if (chip->dac_volume[0] != data->wm8776_regs[WM8776_DACLVOL] ||
+		    chip->dac_volume[1] != data->wm8776_regs[WM8776_DACRVOL]) {
+			wm8776_write(chip, WM8776_DACMASTER,
+				     chip->dac_volume[0] | WM8776_UPDATE);
+			data->wm8776_regs[WM8776_DACLVOL] = chip->dac_volume[0];
+			data->wm8776_regs[WM8776_DACRVOL] = chip->dac_volume[0];
+		}
+	} else {
+		to_change = (chip->dac_volume[0] !=
+			     data->wm8776_regs[WM8776_DACLVOL]) << 0;
+		to_change |= (chip->dac_volume[1] !=
+			      data->wm8776_regs[WM8776_DACLVOL]) << 1;
+		if (to_change & 1)
+			wm8776_write(chip, WM8776_DACLVOL, chip->dac_volume[0] |
+				     ((to_change & 2) ? 0 : WM8776_UPDATE));
+		if (to_change & 2)
+			wm8776_write(chip, WM8776_DACRVOL,
+				     chip->dac_volume[1] | WM8776_UPDATE);
+	}
+}
+
+static void update_wm87x6_volume(struct oxygen *chip)
+{
+	static const u8 wm8766_regs[6] = {
+		WM8766_LDA1, WM8766_RDA1,
+		WM8766_LDA2, WM8766_RDA2,
+		WM8766_LDA3, WM8766_RDA3,
+	};
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int i;
+	u8 to_change;
+
+	update_wm8776_volume(chip);
+	if (chip->dac_volume[2] == chip->dac_volume[3] &&
+	    chip->dac_volume[2] == chip->dac_volume[4] &&
+	    chip->dac_volume[2] == chip->dac_volume[5] &&
+	    chip->dac_volume[2] == chip->dac_volume[6] &&
+	    chip->dac_volume[2] == chip->dac_volume[7]) {
+		to_change = 0;
+		for (i = 0; i < 6; ++i)
+			if (chip->dac_volume[2] !=
+			    data->wm8766_regs[wm8766_regs[i]])
+				to_change = 1;
+		if (to_change) {
+			wm8766_write(chip, WM8766_MASTDA,
+				     chip->dac_volume[2] | WM8766_UPDATE);
+			for (i = 0; i < 6; ++i)
+				data->wm8766_regs[wm8766_regs[i]] =
+					chip->dac_volume[2];
+		}
+	} else {
+		to_change = 0;
+		for (i = 0; i < 6; ++i)
+			to_change |= (chip->dac_volume[2 + i] !=
+				      data->wm8766_regs[wm8766_regs[i]]) << i;
+		for (i = 0; i < 6; ++i)
+			if (to_change & (1 << i))
+				wm8766_write(chip, wm8766_regs[i],
+					     chip->dac_volume[2 + i] |
+					     ((to_change & (0x3e << i))
+					      ? 0 : WM8766_UPDATE));
+	}
+}
+
+static void update_wm8776_mute(struct oxygen *chip)
+{
+	wm8776_write_cached(chip, WM8776_DACMUTE,
+			    chip->dac_mute ? WM8776_DMUTE : 0);
+}
+
+static void update_wm87x6_mute(struct oxygen *chip)
+{
+	update_wm8776_mute(chip);
+	wm8766_write_cached(chip, WM8766_DAC_CTRL2, WM8766_ZCD |
+			    (chip->dac_mute ? WM8766_DMUTE_MASK : 0));
+}
+
+static void update_wm8766_center_lfe_mix(struct oxygen *chip, bool mixed)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int reg;
+
+	/*
+	 * The WM8766 can mix left and right channels, but this setting
+	 * applies to all three stereo pairs.
+	 */
+	reg = data->wm8766_regs[WM8766_DAC_CTRL] &
+		~(WM8766_PL_LEFT_MASK | WM8766_PL_RIGHT_MASK);
+	if (mixed)
+		reg |= WM8766_PL_LEFT_LRMIX | WM8766_PL_RIGHT_LRMIX;
+	else
+		reg |= WM8766_PL_LEFT_LEFT | WM8766_PL_RIGHT_RIGHT;
+	wm8766_write_cached(chip, WM8766_DAC_CTRL, reg);
+}
+
+static void xonar_ds_gpio_changed(struct oxygen *chip)
+{
+	xonar_ds_handle_hp_jack(chip);
+}
+
+static int wm8776_bit_switch_get(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	u16 bit = ctl->private_value & 0xffff;
+	unsigned int reg_index = (ctl->private_value >> 16) & 0xff;
+	bool invert = (ctl->private_value >> 24) & 1;
+
+	value->value.integer.value[0] =
+		((data->wm8776_regs[reg_index] & bit) != 0) ^ invert;
+	return 0;
+}
+
+static int wm8776_bit_switch_put(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	u16 bit = ctl->private_value & 0xffff;
+	u16 reg_value;
+	unsigned int reg_index = (ctl->private_value >> 16) & 0xff;
+	bool invert = (ctl->private_value >> 24) & 1;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	reg_value = data->wm8776_regs[reg_index] & ~bit;
+	if (value->value.integer.value[0] ^ invert)
+		reg_value |= bit;
+	changed = reg_value != data->wm8776_regs[reg_index];
+	if (changed)
+		wm8776_write(chip, reg_index, reg_value);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int wm8776_field_enum_info(struct snd_kcontrol *ctl,
+				  struct snd_ctl_elem_info *info)
+{
+	static const char *const hld[16] = {
+		"0 ms", "2.67 ms", "5.33 ms", "10.6 ms",
+		"21.3 ms", "42.7 ms", "85.3 ms", "171 ms",
+		"341 ms", "683 ms", "1.37 s", "2.73 s",
+		"5.46 s", "10.9 s", "21.8 s", "43.7 s",
+	};
+	static const char *const atk_lim[11] = {
+		"0.25 ms", "0.5 ms", "1 ms", "2 ms",
+		"4 ms", "8 ms", "16 ms", "32 ms",
+		"64 ms", "128 ms", "256 ms",
+	};
+	static const char *const atk_alc[11] = {
+		"8.40 ms", "16.8 ms", "33.6 ms", "67.2 ms",
+		"134 ms", "269 ms", "538 ms", "1.08 s",
+		"2.15 s", "4.3 s", "8.6 s",
+	};
+	static const char *const dcy_lim[11] = {
+		"1.2 ms", "2.4 ms", "4.8 ms", "9.6 ms",
+		"19.2 ms", "38.4 ms", "76.8 ms", "154 ms",
+		"307 ms", "614 ms", "1.23 s",
+	};
+	static const char *const dcy_alc[11] = {
+		"33.5 ms", "67.0 ms", "134 ms", "268 ms",
+		"536 ms", "1.07 s", "2.14 s", "4.29 s",
+		"8.58 s", "17.2 s", "34.3 s",
+	};
+	static const char *const tranwin[8] = {
+		"0 us", "62.5 us", "125 us", "250 us",
+		"500 us", "1 ms", "2 ms", "4 ms",
+	};
+	u8 max;
+	const char *const *names;
+
+	max = (ctl->private_value >> 12) & 0xf;
+	switch ((ctl->private_value >> 24) & 0x1f) {
+	case WM8776_ALCCTRL2:
+		names = hld;
+		break;
+	case WM8776_ALCCTRL3:
+		if (((ctl->private_value >> 20) & 0xf) == 0) {
+			if (ctl->private_value & LC_CONTROL_LIMITER)
+				names = atk_lim;
+			else
+				names = atk_alc;
+		} else {
+			if (ctl->private_value & LC_CONTROL_LIMITER)
+				names = dcy_lim;
+			else
+				names = dcy_alc;
+		}
+		break;
+	case WM8776_LIMITER:
+		names = tranwin;
+		break;
+	default:
+		return -ENXIO;
+	}
+	return snd_ctl_enum_info(info, 1, max + 1, names);
+}
+
+static int wm8776_field_volume_info(struct snd_kcontrol *ctl,
+				    struct snd_ctl_elem_info *info)
+{
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = 1;
+	info->value.integer.min = (ctl->private_value >> 8) & 0xf;
+	info->value.integer.max = (ctl->private_value >> 12) & 0xf;
+	return 0;
+}
+
+static void wm8776_field_set_from_ctl(struct snd_kcontrol *ctl)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int value, reg_index, mode;
+	u8 min, max, shift;
+	u16 mask, reg_value;
+	bool invert;
+
+	if ((data->wm8776_regs[WM8776_ALCCTRL1] & WM8776_LCSEL_MASK) ==
+	    WM8776_LCSEL_LIMITER)
+		mode = LC_CONTROL_LIMITER;
+	else
+		mode = LC_CONTROL_ALC;
+	if (!(ctl->private_value & mode))
+		return;
+
+	value = ctl->private_value & 0xf;
+	min = (ctl->private_value >> 8) & 0xf;
+	max = (ctl->private_value >> 12) & 0xf;
+	mask = (ctl->private_value >> 16) & 0xf;
+	shift = (ctl->private_value >> 20) & 0xf;
+	reg_index = (ctl->private_value >> 24) & 0x1f;
+	invert = (ctl->private_value >> 29) & 0x1;
+
+	if (invert)
+		value = max - (value - min);
+	reg_value = data->wm8776_regs[reg_index];
+	reg_value &= ~(mask << shift);
+	reg_value |= value << shift;
+	wm8776_write_cached(chip, reg_index, reg_value);
+}
+
+static int wm8776_field_set(struct snd_kcontrol *ctl, unsigned int value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u8 min, max;
+	int changed;
+
+	min = (ctl->private_value >> 8) & 0xf;
+	max = (ctl->private_value >> 12) & 0xf;
+	if (value < min || value > max)
+		return -EINVAL;
+	mutex_lock(&chip->mutex);
+	changed = value != (ctl->private_value & 0xf);
+	if (changed) {
+		ctl->private_value = (ctl->private_value & ~0xf) | value;
+		wm8776_field_set_from_ctl(ctl);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int wm8776_field_enum_get(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_value *value)
+{
+	value->value.enumerated.item[0] = ctl->private_value & 0xf;
+	return 0;
+}
+
+static int wm8776_field_volume_get(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_value *value)
+{
+	value->value.integer.value[0] = ctl->private_value & 0xf;
+	return 0;
+}
+
+static int wm8776_field_enum_put(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_value *value)
+{
+	return wm8776_field_set(ctl, value->value.enumerated.item[0]);
+}
+
+static int wm8776_field_volume_put(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_value *value)
+{
+	return wm8776_field_set(ctl, value->value.integer.value[0]);
+}
+
+static int wm8776_hp_vol_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 = 0x79 - 60;
+	info->value.integer.max = 0x7f;
+	return 0;
+}
+
+static int wm8776_hp_vol_get(struct snd_kcontrol *ctl,
+			     struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	mutex_lock(&chip->mutex);
+	value->value.integer.value[0] =
+		data->wm8776_regs[WM8776_HPLVOL] & WM8776_HPATT_MASK;
+	value->value.integer.value[1] =
+		data->wm8776_regs[WM8776_HPRVOL] & WM8776_HPATT_MASK;
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int wm8776_hp_vol_put(struct snd_kcontrol *ctl,
+			     struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	u8 to_update;
+
+	mutex_lock(&chip->mutex);
+	to_update = (value->value.integer.value[0] !=
+		     (data->wm8776_regs[WM8776_HPLVOL] & WM8776_HPATT_MASK))
+		<< 0;
+	to_update |= (value->value.integer.value[1] !=
+		      (data->wm8776_regs[WM8776_HPRVOL] & WM8776_HPATT_MASK))
+		<< 1;
+	if (value->value.integer.value[0] == value->value.integer.value[1]) {
+		if (to_update) {
+			wm8776_write(chip, WM8776_HPMASTER,
+				     value->value.integer.value[0] |
+				     WM8776_HPZCEN | WM8776_UPDATE);
+			data->wm8776_regs[WM8776_HPLVOL] =
+				value->value.integer.value[0] | WM8776_HPZCEN;
+			data->wm8776_regs[WM8776_HPRVOL] =
+				value->value.integer.value[0] | WM8776_HPZCEN;
+		}
+	} else {
+		if (to_update & 1)
+			wm8776_write(chip, WM8776_HPLVOL,
+				     value->value.integer.value[0] |
+				     WM8776_HPZCEN |
+				     ((to_update & 2) ? 0 : WM8776_UPDATE));
+		if (to_update & 2)
+			wm8776_write(chip, WM8776_HPRVOL,
+				     value->value.integer.value[1] |
+				     WM8776_HPZCEN | WM8776_UPDATE);
+	}
+	mutex_unlock(&chip->mutex);
+	return to_update != 0;
+}
+
+static int wm8776_input_mux_get(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int mux_bit = ctl->private_value;
+
+	value->value.integer.value[0] =
+		!!(data->wm8776_regs[WM8776_ADCMUX] & mux_bit);
+	return 0;
+}
+
+static int wm8776_input_mux_put(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	struct snd_kcontrol *other_ctl;
+	unsigned int mux_bit = ctl->private_value;
+	u16 reg;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	reg = data->wm8776_regs[WM8776_ADCMUX];
+	if (value->value.integer.value[0]) {
+		reg |= mux_bit;
+		/* line-in and mic-in are exclusive */
+		mux_bit ^= 3;
+		if (reg & mux_bit) {
+			reg &= ~mux_bit;
+			if (mux_bit == 1)
+				other_ctl = data->line_adcmux_control;
+			else
+				other_ctl = data->mic_adcmux_control;
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &other_ctl->id);
+		}
+	} else
+		reg &= ~mux_bit;
+	changed = reg != data->wm8776_regs[WM8776_ADCMUX];
+	if (changed) {
+		oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+				      reg & 1 ? GPIO_DS_INPUT_ROUTE : 0,
+				      GPIO_DS_INPUT_ROUTE);
+		wm8776_write(chip, WM8776_ADCMUX, reg);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int wm8776_input_vol_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 = 0xa5;
+	info->value.integer.max = 0xff;
+	return 0;
+}
+
+static int wm8776_input_vol_get(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	mutex_lock(&chip->mutex);
+	value->value.integer.value[0] =
+		data->wm8776_regs[WM8776_ADCLVOL] & WM8776_AGMASK;
+	value->value.integer.value[1] =
+		data->wm8776_regs[WM8776_ADCRVOL] & WM8776_AGMASK;
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int wm8776_input_vol_put(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	int changed = 0;
+
+	mutex_lock(&chip->mutex);
+	changed = (value->value.integer.value[0] !=
+		   (data->wm8776_regs[WM8776_ADCLVOL] & WM8776_AGMASK)) ||
+		  (value->value.integer.value[1] !=
+		   (data->wm8776_regs[WM8776_ADCRVOL] & WM8776_AGMASK));
+	wm8776_write_cached(chip, WM8776_ADCLVOL,
+			    value->value.integer.value[0] | WM8776_ZCA);
+	wm8776_write_cached(chip, WM8776_ADCRVOL,
+			    value->value.integer.value[1] | WM8776_ZCA);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int wm8776_level_control_info(struct snd_kcontrol *ctl,
+				     struct snd_ctl_elem_info *info)
+{
+	static const char *const names[3] = {
+		"None", "Peak Limiter", "Automatic Level Control"
+	};
+
+	return snd_ctl_enum_info(info, 1, 3, names);
+}
+
+static int wm8776_level_control_get(struct snd_kcontrol *ctl,
+				    struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	if (!(data->wm8776_regs[WM8776_ALCCTRL2] & WM8776_LCEN))
+		value->value.enumerated.item[0] = 0;
+	else if ((data->wm8776_regs[WM8776_ALCCTRL1] & WM8776_LCSEL_MASK) ==
+		 WM8776_LCSEL_LIMITER)
+		value->value.enumerated.item[0] = 1;
+	else
+		value->value.enumerated.item[0] = 2;
+	return 0;
+}
+
+static void activate_control(struct oxygen *chip,
+			     struct snd_kcontrol *ctl, unsigned int mode)
+{
+	unsigned int access;
+
+	if (ctl->private_value & mode)
+		access = 0;
+	else
+		access = SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	if ((ctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_INACTIVE) != access) {
+		ctl->vd[0].access ^= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
+	}
+}
+
+static int wm8776_level_control_put(struct snd_kcontrol *ctl,
+				    struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int mode = 0, i;
+	u16 ctrl1, ctrl2;
+	int changed;
+
+	if (value->value.enumerated.item[0] >= 3)
+		return -EINVAL;
+	mutex_lock(&chip->mutex);
+	changed = value->value.enumerated.item[0] != ctl->private_value;
+	if (changed) {
+		ctl->private_value = value->value.enumerated.item[0];
+		ctrl1 = data->wm8776_regs[WM8776_ALCCTRL1];
+		ctrl2 = data->wm8776_regs[WM8776_ALCCTRL2];
+		switch (value->value.enumerated.item[0]) {
+		default:
+			wm8776_write_cached(chip, WM8776_ALCCTRL2,
+					    ctrl2 & ~WM8776_LCEN);
+			break;
+		case 1:
+			wm8776_write_cached(chip, WM8776_ALCCTRL1,
+					    (ctrl1 & ~WM8776_LCSEL_MASK) |
+					    WM8776_LCSEL_LIMITER);
+			wm8776_write_cached(chip, WM8776_ALCCTRL2,
+					    ctrl2 | WM8776_LCEN);
+			mode = LC_CONTROL_LIMITER;
+			break;
+		case 2:
+			wm8776_write_cached(chip, WM8776_ALCCTRL1,
+					    (ctrl1 & ~WM8776_LCSEL_MASK) |
+					    WM8776_LCSEL_ALC_STEREO);
+			wm8776_write_cached(chip, WM8776_ALCCTRL2,
+					    ctrl2 | WM8776_LCEN);
+			mode = LC_CONTROL_ALC;
+			break;
+		}
+		for (i = 0; i < ARRAY_SIZE(data->lc_controls); ++i)
+			activate_control(chip, data->lc_controls[i], mode);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int hpf_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)
+{
+	static const char *const names[2] = {
+		"None", "High-pass Filter"
+	};
+
+	return snd_ctl_enum_info(info, 1, 2, names);
+}
+
+static int hpf_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	value->value.enumerated.item[0] =
+		!(data->wm8776_regs[WM8776_ADCIFCTRL] & WM8776_ADCHPD);
+	return 0;
+}
+
+static int hpf_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int reg;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	reg = data->wm8776_regs[WM8776_ADCIFCTRL] & ~WM8776_ADCHPD;
+	if (!value->value.enumerated.item[0])
+		reg |= WM8776_ADCHPD;
+	changed = reg != data->wm8776_regs[WM8776_ADCIFCTRL];
+	if (changed)
+		wm8776_write(chip, WM8776_ADCIFCTRL, reg);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+#define WM8776_BIT_SWITCH(xname, reg, bit, invert, flags) { \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = xname, \
+	.info = snd_ctl_boolean_mono_info, \
+	.get = wm8776_bit_switch_get, \
+	.put = wm8776_bit_switch_put, \
+	.private_value = ((reg) << 16) | (bit) | ((invert) << 24) | (flags), \
+}
+#define _WM8776_FIELD_CTL(xname, reg, shift, initval, min, max, mask, flags) \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = xname, \
+	.private_value = (initval) | ((min) << 8) | ((max) << 12) | \
+	((mask) << 16) | ((shift) << 20) | ((reg) << 24) | (flags)
+#define WM8776_FIELD_CTL_ENUM(xname, reg, shift, init, min, max, mask, flags) {\
+	_WM8776_FIELD_CTL(xname " Capture Enum", \
+			  reg, shift, init, min, max, mask, flags), \
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+		  SNDRV_CTL_ELEM_ACCESS_INACTIVE, \
+	.info = wm8776_field_enum_info, \
+	.get = wm8776_field_enum_get, \
+	.put = wm8776_field_enum_put, \
+}
+#define WM8776_FIELD_CTL_VOLUME(a, b, c, d, e, f, g, h, tlv_p) { \
+	_WM8776_FIELD_CTL(a " Capture Volume", b, c, d, e, f, g, h), \
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+		  SNDRV_CTL_ELEM_ACCESS_INACTIVE | \
+		  SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+	.info = wm8776_field_volume_info, \
+	.get = wm8776_field_volume_get, \
+	.put = wm8776_field_volume_put, \
+	.tlv = { .p = tlv_p }, \
+}
+
+static const DECLARE_TLV_DB_SCALE(wm87x6_dac_db_scale, -6000, 50, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_adc_db_scale, -2100, 50, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_hp_db_scale, -6000, 100, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_lct_db_scale, -1600, 100, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_maxgain_db_scale, 0, 400, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_ngth_db_scale, -7800, 600, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_lim_db_scale, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_alc_db_scale, -2100, 400, 0);
+
+static const struct snd_kcontrol_new ds_controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Headphone Playback Volume",
+		.info = wm8776_hp_vol_info,
+		.get = wm8776_hp_vol_get,
+		.put = wm8776_hp_vol_put,
+		.tlv = { .p = wm8776_hp_db_scale },
+	},
+	WM8776_BIT_SWITCH("Headphone Playback Switch",
+			  WM8776_PWRDOWN, WM8776_HPPD, 1, 0),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Input Capture Volume",
+		.info = wm8776_input_vol_info,
+		.get = wm8776_input_vol_get,
+		.put = wm8776_input_vol_put,
+		.tlv = { .p = wm8776_adc_db_scale },
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Line Capture Switch",
+		.info = snd_ctl_boolean_mono_info,
+		.get = wm8776_input_mux_get,
+		.put = wm8776_input_mux_put,
+		.private_value = 1 << 0,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Mic Capture Switch",
+		.info = snd_ctl_boolean_mono_info,
+		.get = wm8776_input_mux_get,
+		.put = wm8776_input_mux_put,
+		.private_value = 1 << 1,
+	},
+	WM8776_BIT_SWITCH("Front Mic Capture Switch",
+			  WM8776_ADCMUX, 1 << 2, 0, 0),
+	WM8776_BIT_SWITCH("Aux Capture Switch",
+			  WM8776_ADCMUX, 1 << 3, 0, 0),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "ADC Filter Capture Enum",
+		.info = hpf_info,
+		.get = hpf_get,
+		.put = hpf_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Level Control Capture Enum",
+		.info = wm8776_level_control_info,
+		.get = wm8776_level_control_get,
+		.put = wm8776_level_control_put,
+		.private_value = 0,
+	},
+};
+static const struct snd_kcontrol_new hdav_slim_controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "HDMI Playback Switch",
+		.info = snd_ctl_boolean_mono_info,
+		.get = xonar_gpio_bit_switch_get,
+		.put = xonar_gpio_bit_switch_put,
+		.private_value = GPIO_SLIM_HDMI_DISABLE | XONAR_GPIO_BIT_INVERT,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Headphone Playback Volume",
+		.info = wm8776_hp_vol_info,
+		.get = wm8776_hp_vol_get,
+		.put = wm8776_hp_vol_put,
+		.tlv = { .p = wm8776_hp_db_scale },
+	},
+	WM8776_BIT_SWITCH("Headphone Playback Switch",
+			  WM8776_PWRDOWN, WM8776_HPPD, 1, 0),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Input Capture Volume",
+		.info = wm8776_input_vol_info,
+		.get = wm8776_input_vol_get,
+		.put = wm8776_input_vol_put,
+		.tlv = { .p = wm8776_adc_db_scale },
+	},
+	WM8776_BIT_SWITCH("Mic Capture Switch",
+			  WM8776_ADCMUX, 1 << 0, 0, 0),
+	WM8776_BIT_SWITCH("Aux Capture Switch",
+			  WM8776_ADCMUX, 1 << 1, 0, 0),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "ADC Filter Capture Enum",
+		.info = hpf_info,
+		.get = hpf_get,
+		.put = hpf_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Level Control Capture Enum",
+		.info = wm8776_level_control_info,
+		.get = wm8776_level_control_get,
+		.put = wm8776_level_control_put,
+		.private_value = 0,
+	},
+};
+static const struct snd_kcontrol_new lc_controls[] = {
+	WM8776_FIELD_CTL_VOLUME("Limiter Threshold",
+				WM8776_ALCCTRL1, 0, 11, 0, 15, 0xf,
+				LC_CONTROL_LIMITER, wm8776_lct_db_scale),
+	WM8776_FIELD_CTL_ENUM("Limiter Attack Time",
+			      WM8776_ALCCTRL3, 0, 2, 0, 10, 0xf,
+			      LC_CONTROL_LIMITER),
+	WM8776_FIELD_CTL_ENUM("Limiter Decay Time",
+			      WM8776_ALCCTRL3, 4, 3, 0, 10, 0xf,
+			      LC_CONTROL_LIMITER),
+	WM8776_FIELD_CTL_ENUM("Limiter Transient Window",
+			      WM8776_LIMITER, 4, 2, 0, 7, 0x7,
+			      LC_CONTROL_LIMITER),
+	WM8776_FIELD_CTL_VOLUME("Limiter Maximum Attenuation",
+				WM8776_LIMITER, 0, 6, 3, 12, 0xf,
+				LC_CONTROL_LIMITER,
+				wm8776_maxatten_lim_db_scale),
+	WM8776_FIELD_CTL_VOLUME("ALC Target Level",
+				WM8776_ALCCTRL1, 0, 11, 0, 15, 0xf,
+				LC_CONTROL_ALC, wm8776_lct_db_scale),
+	WM8776_FIELD_CTL_ENUM("ALC Attack Time",
+			      WM8776_ALCCTRL3, 0, 2, 0, 10, 0xf,
+			      LC_CONTROL_ALC),
+	WM8776_FIELD_CTL_ENUM("ALC Decay Time",
+			      WM8776_ALCCTRL3, 4, 3, 0, 10, 0xf,
+			      LC_CONTROL_ALC),
+	WM8776_FIELD_CTL_VOLUME("ALC Maximum Gain",
+				WM8776_ALCCTRL1, 4, 7, 1, 7, 0x7,
+				LC_CONTROL_ALC, wm8776_maxgain_db_scale),
+	WM8776_FIELD_CTL_VOLUME("ALC Maximum Attenuation",
+				WM8776_LIMITER, 0, 10, 10, 15, 0xf,
+				LC_CONTROL_ALC, wm8776_maxatten_alc_db_scale),
+	WM8776_FIELD_CTL_ENUM("ALC Hold Time",
+			      WM8776_ALCCTRL2, 0, 0, 0, 15, 0xf,
+			      LC_CONTROL_ALC),
+	WM8776_BIT_SWITCH("Noise Gate Capture Switch",
+			  WM8776_NOISEGATE, WM8776_NGAT, 0,
+			  LC_CONTROL_ALC),
+	WM8776_FIELD_CTL_VOLUME("Noise Gate Threshold",
+				WM8776_NOISEGATE, 2, 0, 0, 7, 0x7,
+				LC_CONTROL_ALC, wm8776_ngth_db_scale),
+};
+
+static int add_lc_controls(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int i;
+	struct snd_kcontrol *ctl;
+	int err;
+
+	BUILD_BUG_ON(ARRAY_SIZE(lc_controls) != ARRAY_SIZE(data->lc_controls));
+	for (i = 0; i < ARRAY_SIZE(lc_controls); ++i) {
+		ctl = snd_ctl_new1(&lc_controls[i], chip);
+		if (!ctl)
+			return -ENOMEM;
+		err = snd_ctl_add(chip->card, ctl);
+		if (err < 0)
+			return err;
+		data->lc_controls[i] = ctl;
+	}
+	return 0;
+}
+
+static int xonar_ds_mixer_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int i;
+	struct snd_kcontrol *ctl;
+	int err;
+
+	for (i = 0; i < ARRAY_SIZE(ds_controls); ++i) {
+		ctl = snd_ctl_new1(&ds_controls[i], chip);
+		if (!ctl)
+			return -ENOMEM;
+		err = snd_ctl_add(chip->card, ctl);
+		if (err < 0)
+			return err;
+		if (!strcmp(ctl->id.name, "Line Capture Switch"))
+			data->line_adcmux_control = ctl;
+		else if (!strcmp(ctl->id.name, "Mic Capture Switch"))
+			data->mic_adcmux_control = ctl;
+	}
+	if (!data->line_adcmux_control || !data->mic_adcmux_control)
+		return -ENXIO;
+
+	return add_lc_controls(chip);
+}
+
+static int xonar_hdav_slim_mixer_init(struct oxygen *chip)
+{
+	unsigned int i;
+	struct snd_kcontrol *ctl;
+	int err;
+
+	for (i = 0; i < ARRAY_SIZE(hdav_slim_controls); ++i) {
+		ctl = snd_ctl_new1(&hdav_slim_controls[i], chip);
+		if (!ctl)
+			return -ENOMEM;
+		err = snd_ctl_add(chip->card, ctl);
+		if (err < 0)
+			return err;
+	}
+
+	return add_lc_controls(chip);
+}
+
+static void dump_wm8776_registers(struct oxygen *chip,
+				  struct snd_info_buffer *buffer)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int i;
+
+	snd_iprintf(buffer, "\nWM8776:\n00:");
+	for (i = 0; i < 0x10; ++i)
+		snd_iprintf(buffer, " %03x", data->wm8776_regs[i]);
+	snd_iprintf(buffer, "\n10:");
+	for (i = 0x10; i < 0x17; ++i)
+		snd_iprintf(buffer, " %03x", data->wm8776_regs[i]);
+	snd_iprintf(buffer, "\n");
+}
+
+static void dump_wm87x6_registers(struct oxygen *chip,
+				  struct snd_info_buffer *buffer)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int i;
+
+	dump_wm8776_registers(chip, buffer);
+	snd_iprintf(buffer, "\nWM8766:\n00:");
+	for (i = 0; i < 0x10; ++i)
+		snd_iprintf(buffer, " %03x", data->wm8766_regs[i]);
+	snd_iprintf(buffer, "\n");
+}
+
+static const struct oxygen_model model_xonar_ds = {
+	.longname = "Asus Virtuoso 66",
+	.chip = "AV200",
+	.init = xonar_ds_init,
+	.mixer_init = xonar_ds_mixer_init,
+	.cleanup = xonar_ds_cleanup,
+	.suspend = xonar_ds_suspend,
+	.resume = xonar_ds_resume,
+	.pcm_hardware_filter = wm8776_adc_hardware_filter,
+	.set_dac_params = set_wm87x6_dac_params,
+	.set_adc_params = set_wm8776_adc_params,
+	.update_dac_volume = update_wm87x6_volume,
+	.update_dac_mute = update_wm87x6_mute,
+	.update_center_lfe_mix = update_wm8766_center_lfe_mix,
+	.gpio_changed = xonar_ds_gpio_changed,
+	.dump_registers = dump_wm87x6_registers,
+	.dac_tlv = wm87x6_dac_db_scale,
+	.model_data_size = sizeof(struct xonar_wm87x6),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 CAPTURE_0_FROM_I2S_1 |
+			 CAPTURE_1_FROM_SPDIF,
+	.dac_channels_pcm = 8,
+	.dac_channels_mixer = 8,
+	.dac_volume_min = 255 - 2*60,
+	.dac_volume_max = 255,
+	.function_flags = OXYGEN_FUNCTION_SPI,
+	.dac_mclks = OXYGEN_MCLKS(256, 256, 128),
+	.adc_mclks = OXYGEN_MCLKS(256, 256, 128),
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
+
+static const struct oxygen_model model_xonar_hdav_slim = {
+	.shortname = "Xonar HDAV1.3 Slim",
+	.longname = "Asus Virtuoso 200",
+	.chip = "AV200",
+	.init = xonar_hdav_slim_init,
+	.mixer_init = xonar_hdav_slim_mixer_init,
+	.cleanup = xonar_hdav_slim_cleanup,
+	.suspend = xonar_hdav_slim_suspend,
+	.resume = xonar_hdav_slim_resume,
+	.pcm_hardware_filter = xonar_hdav_slim_hardware_filter,
+	.set_dac_params = set_hdav_slim_dac_params,
+	.set_adc_params = set_wm8776_adc_params,
+	.update_dac_volume = update_wm8776_volume,
+	.update_dac_mute = update_wm8776_mute,
+	.uart_input = xonar_hdmi_uart_input,
+	.dump_registers = dump_wm8776_registers,
+	.dac_tlv = wm87x6_dac_db_scale,
+	.model_data_size = sizeof(struct xonar_wm87x6),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 CAPTURE_0_FROM_I2S_1 |
+			 CAPTURE_1_FROM_SPDIF,
+	.dac_channels_pcm = 8,
+	.dac_channels_mixer = 2,
+	.dac_volume_min = 255 - 2*60,
+	.dac_volume_max = 255,
+	.function_flags = OXYGEN_FUNCTION_2WIRE,
+	.dac_mclks = OXYGEN_MCLKS(256, 256, 128),
+	.adc_mclks = OXYGEN_MCLKS(256, 256, 128),
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
+
+int get_xonar_wm87x6_model(struct oxygen *chip,
+			   const struct pci_device_id *id)
+{
+	switch (id->subdevice) {
+	case 0x838e:
+		chip->model = model_xonar_ds;
+		chip->model.shortname = "Xonar DS";
+		break;
+	case 0x8522:
+		chip->model = model_xonar_ds;
+		chip->model.shortname = "Xonar DSX";
+		break;
+	case 0x835e:
+		chip->model = model_xonar_hdav_slim;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}