diff options
| -rw-r--r-- | Documentation/sound/alsa/ALSA-Configuration.txt | 2 | ||||
| -rw-r--r-- | sound/pci/Kconfig | 1 | ||||
| -rw-r--r-- | sound/pci/oxygen/Makefile | 2 | ||||
| -rw-r--r-- | sound/pci/oxygen/virtuoso.c | 3 | ||||
| -rw-r--r-- | sound/pci/oxygen/wm8766.h | 73 | ||||
| -rw-r--r-- | sound/pci/oxygen/wm8776.h | 177 | ||||
| -rw-r--r-- | sound/pci/oxygen/xonar.h | 2 | ||||
| -rw-r--r-- | sound/pci/oxygen/xonar_wm87x6.c | 1021 | 
8 files changed, 1279 insertions, 2 deletions
diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 8923597bd2b..3579e829699 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -1923,7 +1923,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed.    -------------------      Module for sound cards based on the Asus AV100/AV200 chips, -    i.e., Xonar D1, DX, D2, D2X, HDAV1.3 (Deluxe), Essence ST +    i.e., Xonar D1, DX, D2, D2X, DS, HDAV1.3 (Deluxe), Essence ST      (Deluxe) and Essence STX.      This module supports autoprobe and multiple cards. diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index 351654cf7b0..1298c68d6bf 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -789,6 +789,7 @@ config SND_VIRTUOSO  	  Say Y here to include support for sound cards based on the  	  Asus AV100/AV200 chips, i.e., Xonar D1, DX, D2, D2X,  	  Essence ST (Deluxe), and Essence STX. +	  Support for the DS is experimental.  	  Support for the HDAV1.3 (Deluxe) is very experimental.  	  To compile this driver as a module, choose M here: the module diff --git a/sound/pci/oxygen/Makefile b/sound/pci/oxygen/Makefile index 389941cf610..acd8f15f7bf 100644 --- a/sound/pci/oxygen/Makefile +++ b/sound/pci/oxygen/Makefile @@ -2,7 +2,7 @@ snd-oxygen-lib-objs := oxygen_io.o oxygen_lib.o oxygen_mixer.o oxygen_pcm.o  snd-hifier-objs := hifier.o  snd-oxygen-objs := oxygen.o  snd-virtuoso-objs := virtuoso.o xonar_lib.o \ -	xonar_pcm179x.o xonar_cs43xx.o xonar_hdmi.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_HIFIER) += snd-hifier.o diff --git a/sound/pci/oxygen/virtuoso.c b/sound/pci/oxygen/virtuoso.c index 6accaf9580b..563b6f50821 100644 --- a/sound/pci/oxygen/virtuoso.c +++ b/sound/pci/oxygen/virtuoso.c @@ -49,6 +49,7 @@ static struct pci_device_id xonar_ids[] __devinitdata = {  	{ OXYGEN_PCI_SUBID(0x1043, 0x834f) },  	{ OXYGEN_PCI_SUBID(0x1043, 0x835c) },  	{ OXYGEN_PCI_SUBID(0x1043, 0x835d) }, +	{ OXYGEN_PCI_SUBID(0x1043, 0x838e) },  	{ OXYGEN_PCI_SUBID_BROKEN_EEPROM },  	{ }  }; @@ -61,6 +62,8 @@ static int __devinit get_xonar_model(struct oxygen *chip,  		return 0;  	if (get_xonar_cs43xx_model(chip, id) >= 0)  		return 0; +	if (get_xonar_wm87x6_model(chip, id) >= 0) +		return 0;  	return -EINVAL;  } diff --git a/sound/pci/oxygen/wm8766.h b/sound/pci/oxygen/wm8766.h new file mode 100644 index 00000000000..e0e849a7eae --- /dev/null +++ b/sound/pci/oxygen/wm8766.h @@ -0,0 +1,73 @@ +#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 00000000000..1a96f561572 --- /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/xonar.h b/sound/pci/oxygen/xonar.h index 89b3ed814d6..b35343b0a9a 100644 --- a/sound/pci/oxygen/xonar.h +++ b/sound/pci/oxygen/xonar.h @@ -35,6 +35,8 @@ 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 */ diff --git a/sound/pci/oxygen/xonar_wm87x6.c b/sound/pci/oxygen/xonar_wm87x6.c new file mode 100644 index 00000000000..7754db166d9 --- /dev/null +++ b/sound/pci/oxygen/xonar_wm87x6.c @@ -0,0 +1,1021 @@ +/* + * card driver for models with WM8776/WM8766 DACs (Xonar DS) + * + * 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 + * GPIO 6 -> route input jack to input 1/2 (1/0) + * GPIO 7 -> enable output to speakers + * GPIO 8 -> enable output to speakers + */ + +#include <linux/pci.h> +#include <linux/delay.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 "wm8776.h" +#include "wm8766.h" + +#define GPIO_DS_HP_DETECT	0x0010 +#define GPIO_DS_INPUT_ROUTE	0x0040 +#define GPIO_DS_OUTPUT_ENABLE	0x0180 + +#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 *lc_controls[13]; +}; + +static void wm8776_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 | +			 (1 << OXYGEN_SPI_CODEC_SHIFT) | +			 OXYGEN_SPI_CEN_LATCH_CLOCK_LO, +			 (reg << 9) | 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)) +		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]) { +		if ((reg >= WM8766_LDA1 && reg <= WM8766_RDA1) || +		    (reg >= WM8766_LDA2 && reg <= WM8766_MASTDA)) +			value &= ~WM8766_UPDATE; +		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_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) +{ +	wm8766_write(chip, WM8766_RESET, 0); +	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 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_registers_init(chip); + +	oxygen_write16_masked(chip, OXYGEN_GPIO_CONTROL, GPIO_DS_INPUT_ROUTE, +			      GPIO_DS_HP_DETECT | GPIO_DS_INPUT_ROUTE); +	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_component_add(chip->card, "WM8776"); +	snd_component_add(chip->card, "WM8766"); +} + +static void xonar_ds_cleanup(struct oxygen *chip) +{ +	xonar_disable_output(chip); +} + +static void xonar_ds_suspend(struct oxygen *chip) +{ +	xonar_ds_cleanup(chip); +} + +static void xonar_ds_resume(struct oxygen *chip) +{ +	wm8776_registers_init(chip); +	wm8766_registers_init(chip); +	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 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 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 xonar_ds_gpio_changed(struct oxygen *chip) +{ +	u16 bits; + +	bits = oxygen_read16(chip, OXYGEN_GPIO_DATA); +	snd_printk(KERN_INFO "HP detect: %d\n", !!(bits & GPIO_DS_HP_DETECT)); +} + +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; +	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; +	info->count = 1; +	info->value.enumerated.items = max + 1; +	if (info->value.enumerated.item > max) +		info->value.enumerated.item = max; +	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; +	} +	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]); +	return 0; +} + +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; +	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 &= ~0x003; +		reg |= mux_bit; +	} 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" +	}; +	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; +	info->count = 1; +	info->value.enumerated.items = 3; +	if (info->value.enumerated.item >= 3) +		info->value.enumerated.item = 2; +	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]); +	return 0; +} + +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" +	}; + +	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; +	info->count = 1; +	info->value.enumerated.items = 2; +	if (info->value.enumerated.item >= 2) +		info->value.enumerated.item = 1; +	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]); +	return 0; +} + +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("Aux", WM8776_ADCMUX, 1 << 2, 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 xonar_ds_control_filter(struct snd_kcontrol_new *template) +{ +	if (!strncmp(template->name, "CD Capture ", 11)) +		return 1; /* no CD input */ +	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; +	} +	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 const struct oxygen_model model_xonar_ds = { +	.shortname = "Xonar DS", +	.longname = "Asus Virtuoso 200", +	.chip = "AV200", +	.init = xonar_ds_init, +	.control_filter = xonar_ds_control_filter, +	.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, +	.get_i2s_mclk = oxygen_default_i2s_mclk, +	.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, +	.gpio_changed = xonar_ds_gpio_changed, +	.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, +	.dac_channels = 8, +	.dac_volume_min = 255 - 2*60, +	.dac_volume_max = 255, +	.function_flags = OXYGEN_FUNCTION_SPI, +	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST, +	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST, +}; + +int __devinit get_xonar_wm87x6_model(struct oxygen *chip, +				     const struct pci_device_id *id) +{ +	switch (id->subdevice) { +	case 0x838e: +		chip->model = model_xonar_ds; +		break; +	default: +		return -EINVAL; +	} +	return 0; +}  |