diff options
Diffstat (limited to 'sound/pci/oxygen/xonar_dg.c')
| -rw-r--r-- | sound/pci/oxygen/xonar_dg.c | 295 | 
1 files changed, 295 insertions, 0 deletions
diff --git a/sound/pci/oxygen/xonar_dg.c b/sound/pci/oxygen/xonar_dg.c new file mode 100644 index 00000000000..4cf3200e988 --- /dev/null +++ b/sound/pci/oxygen/xonar_dg.c @@ -0,0 +1,295 @@ +/* + * card driver for the Xonar DG/DGX + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Copyright (c) Roman Volkov <v1ron@mail.ru> + * + *  This driver is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License, version 2. + * + *  This driver is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this driver; if not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Xonar DG/DGX + * ------------ + * + * CS4245 and CS4361 both will mute all outputs if any clock ratio + * is invalid. + * + * CMI8788: + * + *   SPI 0 -> CS4245 + * + *   Playback: + *   I²S 1 -> CS4245 + *   I²S 2 -> CS4361 (center/LFE) + *   I²S 3 -> CS4361 (surround) + *   I²S 4 -> CS4361 (front) + *   Capture: + *   I²S ADC 1 <- CS4245 + * + *   GPIO 3 <- ? + *   GPIO 4 <- headphone detect + *   GPIO 5 -> enable ADC analog circuit for the left channel + *   GPIO 6 -> enable ADC analog circuit for the right channel + *   GPIO 7 -> switch green rear output jack between CS4245 and and the first + *             channel of CS4361 (mechanical relay) + *   GPIO 8 -> enable output to speakers + * + * CS4245: + * + *   input 0 <- mic + *   input 1 <- aux + *   input 2 <- front mic + *   input 4 <- line + *   DAC out -> headphones + *   aux out -> front panel headphones + */ + +#include <linux/pci.h> +#include <linux/delay.h> +#include <sound/control.h> +#include <sound/core.h> +#include <sound/info.h> +#include <sound/pcm.h> +#include <sound/tlv.h> +#include "oxygen.h" +#include "xonar_dg.h" +#include "cs4245.h" + +int cs4245_write_spi(struct oxygen *chip, u8 reg) +{ +	struct dg *data = chip->model_data; +	unsigned int packet; + +	packet = reg << 8; +	packet |= (CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 16; +	packet |= data->cs4245_shadow[reg]; + +	return oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | +				OXYGEN_SPI_DATA_LENGTH_3 | +				OXYGEN_SPI_CLOCK_1280 | +				(0 << OXYGEN_SPI_CODEC_SHIFT) | +				OXYGEN_SPI_CEN_LATCH_CLOCK_HI, +				packet); +} + +int cs4245_read_spi(struct oxygen *chip, u8 addr) +{ +	struct dg *data = chip->model_data; +	int ret; + +	ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | +		OXYGEN_SPI_DATA_LENGTH_2 | +		OXYGEN_SPI_CEN_LATCH_CLOCK_HI | +		OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT), +		((CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 8) | addr); +	if (ret < 0) +		return ret; + +	ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | +		OXYGEN_SPI_DATA_LENGTH_2 | +		OXYGEN_SPI_CEN_LATCH_CLOCK_HI | +		OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT), +		(CS4245_SPI_ADDRESS | CS4245_SPI_READ) << 8); +	if (ret < 0) +		return ret; + +	data->cs4245_shadow[addr] = oxygen_read8(chip, OXYGEN_SPI_DATA1); + +	return 0; +} + +int cs4245_shadow_control(struct oxygen *chip, enum cs4245_shadow_operation op) +{ +	struct dg *data = chip->model_data; +	unsigned char addr; +	int ret; + +	for (addr = 1; addr < ARRAY_SIZE(data->cs4245_shadow); addr++) { +		ret = (op == CS4245_SAVE_TO_SHADOW ? +			cs4245_read_spi(chip, addr) : +			cs4245_write_spi(chip, addr)); +		if (ret < 0) +			return ret; +	} +	return 0; +} + +static void cs4245_init(struct oxygen *chip) +{ +	struct dg *data = chip->model_data; + +	/* save the initial state: codec version, registers */ +	cs4245_shadow_control(chip, CS4245_SAVE_TO_SHADOW); + +	/* +	 * Power up the CODEC internals, enable soft ramp & zero cross, work in +	 * async. mode, enable aux output from DAC. Invert DAC output as in the +	 * Windows driver. +	 */ +	data->cs4245_shadow[CS4245_POWER_CTRL] = 0; +	data->cs4245_shadow[CS4245_SIGNAL_SEL] = +		CS4245_A_OUT_SEL_DAC | CS4245_ASYNCH; +	data->cs4245_shadow[CS4245_DAC_CTRL_1] = +		CS4245_DAC_FM_SINGLE | CS4245_DAC_DIF_LJUST; +	data->cs4245_shadow[CS4245_DAC_CTRL_2] = +		CS4245_DAC_SOFT | CS4245_DAC_ZERO | CS4245_INVERT_DAC; +	data->cs4245_shadow[CS4245_ADC_CTRL] = +		CS4245_ADC_FM_SINGLE | CS4245_ADC_DIF_LJUST; +	data->cs4245_shadow[CS4245_ANALOG_IN] = +		CS4245_PGA_SOFT | CS4245_PGA_ZERO; +	data->cs4245_shadow[CS4245_PGA_B_CTRL] = 0; +	data->cs4245_shadow[CS4245_PGA_A_CTRL] = 0; +	data->cs4245_shadow[CS4245_DAC_A_CTRL] = 8; +	data->cs4245_shadow[CS4245_DAC_B_CTRL] = 8; + +	cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW); +	snd_component_add(chip->card, "CS4245"); +} + +void dg_init(struct oxygen *chip) +{ +	struct dg *data = chip->model_data; + +	data->output_sel = PLAYBACK_DST_HP_FP; +	data->input_sel = CAPTURE_SRC_MIC; + +	cs4245_init(chip); +	oxygen_write16(chip, OXYGEN_GPIO_CONTROL, +		       GPIO_OUTPUT_ENABLE | GPIO_HP_REAR | GPIO_INPUT_ROUTE); +	/* anti-pop delay, wait some time before enabling the output */ +	msleep(2500); +	oxygen_write16(chip, OXYGEN_GPIO_DATA, +		       GPIO_OUTPUT_ENABLE | GPIO_INPUT_ROUTE); +} + +void dg_cleanup(struct oxygen *chip) +{ +	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE); +} + +void dg_suspend(struct oxygen *chip) +{ +	dg_cleanup(chip); +} + +void dg_resume(struct oxygen *chip) +{ +	cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW); +	msleep(2500); +	oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE); +} + +void set_cs4245_dac_params(struct oxygen *chip, +				  struct snd_pcm_hw_params *params) +{ +	struct dg *data = chip->model_data; +	unsigned char dac_ctrl; +	unsigned char mclk_freq; + +	dac_ctrl = data->cs4245_shadow[CS4245_DAC_CTRL_1] & ~CS4245_DAC_FM_MASK; +	mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK1_MASK; +	if (params_rate(params) <= 50000) { +		dac_ctrl |= CS4245_DAC_FM_SINGLE; +		mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT; +	} else if (params_rate(params) <= 100000) { +		dac_ctrl |= CS4245_DAC_FM_DOUBLE; +		mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT; +	} else { +		dac_ctrl |= CS4245_DAC_FM_QUAD; +		mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK1_SHIFT; +	} +	data->cs4245_shadow[CS4245_DAC_CTRL_1] = dac_ctrl; +	data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq; +	cs4245_write_spi(chip, CS4245_DAC_CTRL_1); +	cs4245_write_spi(chip, CS4245_MCLK_FREQ); +} + +void set_cs4245_adc_params(struct oxygen *chip, +				  struct snd_pcm_hw_params *params) +{ +	struct dg *data = chip->model_data; +	unsigned char adc_ctrl; +	unsigned char mclk_freq; + +	adc_ctrl = data->cs4245_shadow[CS4245_ADC_CTRL] & ~CS4245_ADC_FM_MASK; +	mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK2_MASK; +	if (params_rate(params) <= 50000) { +		adc_ctrl |= CS4245_ADC_FM_SINGLE; +		mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT; +	} else if (params_rate(params) <= 100000) { +		adc_ctrl |= CS4245_ADC_FM_DOUBLE; +		mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT; +	} else { +		adc_ctrl |= CS4245_ADC_FM_QUAD; +		mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK2_SHIFT; +	} +	data->cs4245_shadow[CS4245_ADC_CTRL] = adc_ctrl; +	data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq; +	cs4245_write_spi(chip, CS4245_ADC_CTRL); +	cs4245_write_spi(chip, CS4245_MCLK_FREQ); +} + +static inline unsigned int shift_bits(unsigned int value, +				      unsigned int shift_from, +				      unsigned int shift_to, +				      unsigned int mask) +{ +	if (shift_from < shift_to) +		return (value << (shift_to - shift_from)) & mask; +	else +		return (value >> (shift_from - shift_to)) & mask; +} + +unsigned int adjust_dg_dac_routing(struct oxygen *chip, +					  unsigned int play_routing) +{ +	struct dg *data = chip->model_data; + +	switch (data->output_sel) { +	case PLAYBACK_DST_HP: +	case PLAYBACK_DST_HP_FP: +		oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING, +			OXYGEN_PLAY_MUTE23 | OXYGEN_PLAY_MUTE45 | +			OXYGEN_PLAY_MUTE67, OXYGEN_PLAY_MUTE_MASK); +		break; +	case PLAYBACK_DST_MULTICH: +		oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING, +			OXYGEN_PLAY_MUTE01, OXYGEN_PLAY_MUTE_MASK); +		break; +	} +	return (play_routing & OXYGEN_PLAY_DAC0_SOURCE_MASK) | +	       shift_bits(play_routing, +			  OXYGEN_PLAY_DAC2_SOURCE_SHIFT, +			  OXYGEN_PLAY_DAC1_SOURCE_SHIFT, +			  OXYGEN_PLAY_DAC1_SOURCE_MASK) | +	       shift_bits(play_routing, +			  OXYGEN_PLAY_DAC1_SOURCE_SHIFT, +			  OXYGEN_PLAY_DAC2_SOURCE_SHIFT, +			  OXYGEN_PLAY_DAC2_SOURCE_MASK) | +	       shift_bits(play_routing, +			  OXYGEN_PLAY_DAC0_SOURCE_SHIFT, +			  OXYGEN_PLAY_DAC3_SOURCE_SHIFT, +			  OXYGEN_PLAY_DAC3_SOURCE_MASK); +} + +void dump_cs4245_registers(struct oxygen *chip, +				  struct snd_info_buffer *buffer) +{ +	struct dg *data = chip->model_data; +	unsigned int addr; + +	snd_iprintf(buffer, "\nCS4245:"); +	cs4245_read_spi(chip, CS4245_INT_STATUS); +	for (addr = 1; addr < ARRAY_SIZE(data->cs4245_shadow); addr++) +		snd_iprintf(buffer, " %02x", data->cs4245_shadow[addr]); +	snd_iprintf(buffer, "\n"); +}  | 
