| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | /*
 | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  |  * card driver for models with WM8776/WM8766 DACs (Xonar DS/HDAV1.3 Slim) | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  |  * | 
					
						
							|  |  |  |  * 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: | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2010-12-02 11:42:48 +01:00
										 |  |  |  *   SPI 0 -> WM8766 (surround, center/LFE, back) | 
					
						
							|  |  |  |  *   SPI 1 -> WM8776 (front, input) | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2010-12-02 11:42:48 +01:00
										 |  |  |  *   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 | 
					
						
							| 
									
										
										
										
											2010-09-09 12:19:21 +02:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2010-12-02 11:42:48 +01:00
										 |  |  |  * WM8776: | 
					
						
							| 
									
										
										
										
											2010-09-09 12:19:21 +02:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2010-12-02 11:42:48 +01:00
										 |  |  |  *   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 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/pci.h>
 | 
					
						
							|  |  |  | #include <linux/delay.h>
 | 
					
						
							|  |  |  | #include <sound/control.h>
 | 
					
						
							|  |  |  | #include <sound/core.h>
 | 
					
						
							| 
									
										
										
										
											2010-12-02 11:41:10 +01:00
										 |  |  | #include <sound/info.h>
 | 
					
						
							| 
									
										
										
										
											2010-09-09 12:20:29 +02:00
										 |  |  | #include <sound/jack.h>
 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | #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
 | 
					
						
							| 
									
										
										
										
											2010-09-09 12:23:06 +02:00
										 |  |  | #define GPIO_DS_OUTPUT_FRONTLR	0x0080
 | 
					
						
							|  |  |  | #define GPIO_DS_OUTPUT_ENABLE	0x0100
 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | #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 */
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | #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]; | 
					
						
							| 
									
										
										
										
											2010-09-07 13:38:49 +02:00
										 |  |  | 	struct snd_kcontrol *line_adcmux_control; | 
					
						
							|  |  |  | 	struct snd_kcontrol *mic_adcmux_control; | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	struct snd_kcontrol *lc_controls[13]; | 
					
						
							| 
									
										
										
										
											2010-09-09 12:20:29 +02:00
										 |  |  | 	struct snd_jack *hp_jack; | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | 	struct xonar_hdmi hdmi; | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | static void wm8776_write_spi(struct oxygen *chip, | 
					
						
							|  |  |  | 			     unsigned int reg, unsigned int value) | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | { | 
					
						
							|  |  |  | 	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); | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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); | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	if (reg < ARRAY_SIZE(data->wm8776_regs)) { | 
					
						
							| 
									
										
										
										
											2010-03-03 09:16:18 +01:00
										 |  |  | 		if (reg >= WM8776_HPLVOL && reg <= WM8776_DACMASTER) | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 			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); | 
					
						
							| 
									
										
										
										
											2010-09-09 12:18:35 +02:00
										 |  |  | 	if (reg < ARRAY_SIZE(data->wm8766_regs)) { | 
					
						
							|  |  |  | 		if ((reg >= WM8766_LDA1 && reg <= WM8766_RDA1) || | 
					
						
							|  |  |  | 		    (reg >= WM8766_LDA2 && reg <= WM8766_MASTDA)) | 
					
						
							|  |  |  | 			value &= ~WM8766_UPDATE; | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 		data->wm8766_regs[reg] = value; | 
					
						
							| 
									
										
										
										
											2010-09-09 12:18:35 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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) || | 
					
						
							| 
									
										
										
										
											2010-09-09 12:18:35 +02:00
										 |  |  | 	    value != data->wm8766_regs[reg]) | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 		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); | 
					
						
							| 
									
										
										
										
											2012-01-14 16:42:24 +01:00
										 |  |  | 	wm8776_write(chip, WM8776_PHASESWAP, WM8776_PH_MASK); | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	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) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2010-09-09 12:23:06 +02:00
										 |  |  | 	struct xonar_wm87x6 *data = chip->model_data; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	wm8766_write(chip, WM8766_RESET, 0); | 
					
						
							| 
									
										
										
										
											2010-09-09 12:23:06 +02:00
										 |  |  | 	wm8766_write(chip, WM8766_DAC_CTRL, data->wm8766_regs[WM8766_DAC_CTRL]); | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-09 12:23:06 +02:00
										 |  |  | static void wm8766_init(struct oxygen *chip) | 
					
						
							| 
									
										
										
										
											2010-09-09 12:20:29 +02:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct xonar_wm87x6 *data = chip->model_data; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-09 12:23:06 +02:00
										 |  |  | 	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); | 
					
						
							| 
									
										
										
										
											2010-09-09 12:20:29 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 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); | 
					
						
							| 
									
										
										
										
											2010-09-09 12:23:06 +02:00
										 |  |  | 	wm8766_init(chip); | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-09 12:23:06 +02:00
										 |  |  | 	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); | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	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); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-09 12:20:29 +02:00
										 |  |  | 	snd_jack_new(chip->card, "Headphone", | 
					
						
							|  |  |  | 		     SND_JACK_HEADPHONE, &data->hp_jack); | 
					
						
							| 
									
										
										
										
											2010-09-09 12:23:06 +02:00
										 |  |  | 	xonar_ds_handle_hp_jack(chip); | 
					
						
							| 
									
										
										
										
											2010-09-09 12:20:29 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	snd_component_add(chip->card, "WM8776"); | 
					
						
							|  |  |  | 	snd_component_add(chip->card, "WM8766"); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | 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"); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | static void xonar_ds_cleanup(struct oxygen *chip) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	xonar_disable_output(chip); | 
					
						
							| 
									
										
										
										
											2010-09-07 13:37:10 +02:00
										 |  |  | 	wm8776_write(chip, WM8776_RESET, 0); | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | 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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | static void xonar_ds_suspend(struct oxygen *chip) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	xonar_ds_cleanup(chip); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | static void xonar_hdav_slim_suspend(struct oxygen *chip) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	xonar_hdav_slim_cleanup(chip); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | static void xonar_ds_resume(struct oxygen *chip) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	wm8776_registers_init(chip); | 
					
						
							|  |  |  | 	wm8766_registers_init(chip); | 
					
						
							|  |  |  | 	xonar_enable_output(chip); | 
					
						
							| 
									
										
										
										
											2010-09-09 12:23:06 +02:00
										 |  |  | 	xonar_ds_handle_hp_jack(chip); | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | 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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 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; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | 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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | 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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 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)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-09 12:24:35 +02:00
										 |  |  | 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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | static void xonar_ds_gpio_changed(struct oxygen *chip) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2010-09-09 12:23:06 +02:00
										 |  |  | 	xonar_ds_handle_hp_jack(chip); | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2011-01-10 16:25:44 +01:00
										 |  |  | 	return snd_ctl_enum_info(info, 1, max + 1, names); | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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; | 
					
						
							| 
									
										
										
										
											2010-09-07 13:38:49 +02:00
										 |  |  | 	struct snd_kcontrol *other_ctl; | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	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; | 
					
						
							| 
									
										
										
										
											2010-09-07 13:38:49 +02:00
										 |  |  | 		/* 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); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	} 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" | 
					
						
							|  |  |  | 	}; | 
					
						
							| 
									
										
										
										
											2011-01-10 16:25:44 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return snd_ctl_enum_info(info, 1, 3, names); | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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" | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-10 16:25:44 +01:00
										 |  |  | 	return snd_ctl_enum_info(info, 1, 2, names); | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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, | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2010-09-09 12:19:21 +02:00
										 |  |  | 	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), | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	{ | 
					
						
							|  |  |  | 		.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, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | 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, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 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), | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | 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; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 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; | 
					
						
							| 
									
										
										
										
											2010-09-07 13:38:49 +02:00
										 |  |  | 		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; | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2010-09-07 13:38:49 +02:00
										 |  |  | 	if (!data->line_adcmux_control || !data->mic_adcmux_control) | 
					
						
							|  |  |  | 		return -ENXIO; | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	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); | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 		if (!ctl) | 
					
						
							|  |  |  | 			return -ENOMEM; | 
					
						
							|  |  |  | 		err = snd_ctl_add(chip->card, ctl); | 
					
						
							|  |  |  | 		if (err < 0) | 
					
						
							|  |  |  | 			return err; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return add_lc_controls(chip); | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-02 11:41:10 +01:00
										 |  |  | 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"); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | static const struct oxygen_model model_xonar_ds = { | 
					
						
							| 
									
										
										
										
											2010-10-04 13:17:26 +02:00
										 |  |  | 	.longname = "Asus Virtuoso 66", | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	.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, | 
					
						
							| 
									
										
										
										
											2010-09-09 12:24:35 +02:00
										 |  |  | 	.update_center_lfe_mix = update_wm8766_center_lfe_mix, | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	.gpio_changed = xonar_ds_gpio_changed, | 
					
						
							| 
									
										
										
										
											2010-12-02 11:41:10 +01:00
										 |  |  | 	.dump_registers = dump_wm87x6_registers, | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	.dac_tlv = wm87x6_dac_db_scale, | 
					
						
							|  |  |  | 	.model_data_size = sizeof(struct xonar_wm87x6), | 
					
						
							|  |  |  | 	.device_config = PLAYBACK_0_TO_I2S | | 
					
						
							|  |  |  | 			 PLAYBACK_1_TO_SPDIF | | 
					
						
							| 
									
										
										
										
											2011-12-06 10:07:43 +01:00
										 |  |  | 			 CAPTURE_0_FROM_I2S_1 | | 
					
						
							|  |  |  | 			 CAPTURE_1_FROM_SPDIF, | 
					
						
							| 
									
										
										
										
											2011-01-10 15:59:38 +01:00
										 |  |  | 	.dac_channels_pcm = 8, | 
					
						
							|  |  |  | 	.dac_channels_mixer = 8, | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	.dac_volume_min = 255 - 2*60, | 
					
						
							|  |  |  | 	.dac_volume_max = 255, | 
					
						
							|  |  |  | 	.function_flags = OXYGEN_FUNCTION_SPI, | 
					
						
							| 
									
										
										
										
											2011-01-10 16:14:52 +01:00
										 |  |  | 	.dac_mclks = OXYGEN_MCLKS(256, 256, 128), | 
					
						
							|  |  |  | 	.adc_mclks = OXYGEN_MCLKS(256, 256, 128), | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST, | 
					
						
							|  |  |  | 	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | 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 | | 
					
						
							| 
									
										
										
										
											2011-12-06 10:07:43 +01:00
										 |  |  | 			 CAPTURE_0_FROM_I2S_1 | | 
					
						
							|  |  |  | 			 CAPTURE_1_FROM_SPDIF, | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | 	.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, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-12-06 12:35:20 -05:00
										 |  |  | int get_xonar_wm87x6_model(struct oxygen *chip, | 
					
						
							|  |  |  | 			   const struct pci_device_id *id) | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | { | 
					
						
							|  |  |  | 	switch (id->subdevice) { | 
					
						
							|  |  |  | 	case 0x838e: | 
					
						
							|  |  |  | 		chip->model = model_xonar_ds; | 
					
						
							| 
									
										
										
										
											2012-09-09 05:14:15 -04:00
										 |  |  | 		chip->model.shortname = "Xonar DS"; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case 0x8522: | 
					
						
							|  |  |  | 		chip->model = model_xonar_ds; | 
					
						
							|  |  |  | 		chip->model.shortname = "Xonar DSX"; | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 		break; | 
					
						
							| 
									
										
										
										
											2011-01-10 16:23:57 +01:00
										 |  |  | 	case 0x835e: | 
					
						
							|  |  |  | 		chip->model = model_xonar_hdav_slim; | 
					
						
							|  |  |  | 		break; | 
					
						
							| 
									
										
										
										
											2010-01-18 15:44:04 +01:00
										 |  |  | 	default: | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } |