xref: /openbmc/linux/sound/pci/oxygen/xonar_dg.c (revision c7fabbc5)
1b67eb152SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
266410bfdSClemens Ladisch /*
376bc7a0dSClemens Ladisch  * card driver for the Xonar DG/DGX
466410bfdSClemens Ladisch  *
566410bfdSClemens Ladisch  * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
6c4d4390cSRoman Volkov  * Copyright (c) Roman Volkov <v1ron@mail.ru>
766410bfdSClemens Ladisch  */
866410bfdSClemens Ladisch 
966410bfdSClemens Ladisch /*
1076bc7a0dSClemens Ladisch  * Xonar DG/DGX
1176bc7a0dSClemens Ladisch  * ------------
1266410bfdSClemens Ladisch  *
13c4d4390cSRoman Volkov  * CS4245 and CS4361 both will mute all outputs if any clock ratio
14c4d4390cSRoman Volkov  * is invalid.
15c4d4390cSRoman Volkov  *
1666410bfdSClemens Ladisch  * CMI8788:
1766410bfdSClemens Ladisch  *
1866410bfdSClemens Ladisch  *   SPI 0 -> CS4245
1966410bfdSClemens Ladisch  *
20c4d4390cSRoman Volkov  *   Playback:
21efbeb071SClemens Ladisch  *   I²S 1 -> CS4245
22efbeb071SClemens Ladisch  *   I²S 2 -> CS4361 (center/LFE)
23efbeb071SClemens Ladisch  *   I²S 3 -> CS4361 (surround)
24efbeb071SClemens Ladisch  *   I²S 4 -> CS4361 (front)
25c4d4390cSRoman Volkov  *   Capture:
26c4d4390cSRoman Volkov  *   I²S ADC 1 <- CS4245
27efbeb071SClemens Ladisch  *
2866410bfdSClemens Ladisch  *   GPIO 3 <- ?
2966410bfdSClemens Ladisch  *   GPIO 4 <- headphone detect
30c4d4390cSRoman Volkov  *   GPIO 5 -> enable ADC analog circuit for the left channel
31c4d4390cSRoman Volkov  *   GPIO 6 -> enable ADC analog circuit for the right channel
32*c7fabbc5SRandy Dunlap  *   GPIO 7 -> switch green rear output jack between CS4245 and the first
33c4d4390cSRoman Volkov  *             channel of CS4361 (mechanical relay)
3466410bfdSClemens Ladisch  *   GPIO 8 -> enable output to speakers
3566410bfdSClemens Ladisch  *
3666410bfdSClemens Ladisch  * CS4245:
3766410bfdSClemens Ladisch  *
38c4d4390cSRoman Volkov  *   input 0 <- mic
3966410bfdSClemens Ladisch  *   input 1 <- aux
4066410bfdSClemens Ladisch  *   input 2 <- front mic
41c4d4390cSRoman Volkov  *   input 4 <- line
42efbeb071SClemens Ladisch  *   DAC out -> headphones
4366410bfdSClemens Ladisch  *   aux out -> front panel headphones
4466410bfdSClemens Ladisch  */
4566410bfdSClemens Ladisch 
4666410bfdSClemens Ladisch #include <linux/pci.h>
47e92d4575SStephen Rothwell #include <linux/delay.h>
4866410bfdSClemens Ladisch #include <sound/control.h>
4966410bfdSClemens Ladisch #include <sound/core.h>
5066410bfdSClemens Ladisch #include <sound/info.h>
5166410bfdSClemens Ladisch #include <sound/pcm.h>
5266410bfdSClemens Ladisch #include <sound/tlv.h>
5366410bfdSClemens Ladisch #include "oxygen.h"
5466410bfdSClemens Ladisch #include "xonar_dg.h"
5566410bfdSClemens Ladisch #include "cs4245.h"
5666410bfdSClemens Ladisch 
cs4245_write_spi(struct oxygen * chip,u8 reg)57bed61935SRoman Volkov int cs4245_write_spi(struct oxygen *chip, u8 reg)
58bed61935SRoman Volkov {
59bed61935SRoman Volkov 	struct dg *data = chip->model_data;
60bed61935SRoman Volkov 	unsigned int packet;
61bed61935SRoman Volkov 
62bed61935SRoman Volkov 	packet = reg << 8;
63bed61935SRoman Volkov 	packet |= (CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 16;
64bed61935SRoman Volkov 	packet |= data->cs4245_shadow[reg];
65bed61935SRoman Volkov 
66bed61935SRoman Volkov 	return oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
67bed61935SRoman Volkov 				OXYGEN_SPI_DATA_LENGTH_3 |
68bed61935SRoman Volkov 				OXYGEN_SPI_CLOCK_1280 |
69bed61935SRoman Volkov 				(0 << OXYGEN_SPI_CODEC_SHIFT) |
70bed61935SRoman Volkov 				OXYGEN_SPI_CEN_LATCH_CLOCK_HI,
71bed61935SRoman Volkov 				packet);
72bed61935SRoman Volkov }
73bed61935SRoman Volkov 
cs4245_read_spi(struct oxygen * chip,u8 addr)74bed61935SRoman Volkov int cs4245_read_spi(struct oxygen *chip, u8 addr)
75bed61935SRoman Volkov {
76bed61935SRoman Volkov 	struct dg *data = chip->model_data;
77bed61935SRoman Volkov 	int ret;
78bed61935SRoman Volkov 
79bed61935SRoman Volkov 	ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
80bed61935SRoman Volkov 		OXYGEN_SPI_DATA_LENGTH_2 |
81bed61935SRoman Volkov 		OXYGEN_SPI_CEN_LATCH_CLOCK_HI |
82bed61935SRoman Volkov 		OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT),
83bed61935SRoman Volkov 		((CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 8) | addr);
84bed61935SRoman Volkov 	if (ret < 0)
85bed61935SRoman Volkov 		return ret;
86bed61935SRoman Volkov 
87bed61935SRoman Volkov 	ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
88bed61935SRoman Volkov 		OXYGEN_SPI_DATA_LENGTH_2 |
89bed61935SRoman Volkov 		OXYGEN_SPI_CEN_LATCH_CLOCK_HI |
90bed61935SRoman Volkov 		OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT),
91bed61935SRoman Volkov 		(CS4245_SPI_ADDRESS | CS4245_SPI_READ) << 8);
92bed61935SRoman Volkov 	if (ret < 0)
93bed61935SRoman Volkov 		return ret;
94bed61935SRoman Volkov 
95bed61935SRoman Volkov 	data->cs4245_shadow[addr] = oxygen_read8(chip, OXYGEN_SPI_DATA1);
96bed61935SRoman Volkov 
97bed61935SRoman Volkov 	return 0;
98bed61935SRoman Volkov }
99bed61935SRoman Volkov 
cs4245_shadow_control(struct oxygen * chip,enum cs4245_shadow_operation op)100bed61935SRoman Volkov int cs4245_shadow_control(struct oxygen *chip, enum cs4245_shadow_operation op)
101bed61935SRoman Volkov {
102bed61935SRoman Volkov 	struct dg *data = chip->model_data;
103bed61935SRoman Volkov 	unsigned char addr;
104bed61935SRoman Volkov 	int ret;
105bed61935SRoman Volkov 
106bed61935SRoman Volkov 	for (addr = 1; addr < ARRAY_SIZE(data->cs4245_shadow); addr++) {
107bed61935SRoman Volkov 		ret = (op == CS4245_SAVE_TO_SHADOW ?
108bed61935SRoman Volkov 			cs4245_read_spi(chip, addr) :
109bed61935SRoman Volkov 			cs4245_write_spi(chip, addr));
110bed61935SRoman Volkov 		if (ret < 0)
111bed61935SRoman Volkov 			return ret;
112bed61935SRoman Volkov 	}
113bed61935SRoman Volkov 	return 0;
114bed61935SRoman Volkov }
115bed61935SRoman Volkov 
cs4245_init(struct oxygen * chip)11666410bfdSClemens Ladisch static void cs4245_init(struct oxygen *chip)
11766410bfdSClemens Ladisch {
11866410bfdSClemens Ladisch 	struct dg *data = chip->model_data;
11966410bfdSClemens Ladisch 
1203c1611ddSRoman Volkov 	/* save the initial state: codec version, registers */
1213c1611ddSRoman Volkov 	cs4245_shadow_control(chip, CS4245_SAVE_TO_SHADOW);
12266410bfdSClemens Ladisch 
1233c1611ddSRoman Volkov 	/*
1243c1611ddSRoman Volkov 	 * Power up the CODEC internals, enable soft ramp & zero cross, work in
1253c1611ddSRoman Volkov 	 * async. mode, enable aux output from DAC. Invert DAC output as in the
1263c1611ddSRoman Volkov 	 * Windows driver.
1273c1611ddSRoman Volkov 	 */
1283c1611ddSRoman Volkov 	data->cs4245_shadow[CS4245_POWER_CTRL] = 0;
1293c1611ddSRoman Volkov 	data->cs4245_shadow[CS4245_SIGNAL_SEL] =
1303c1611ddSRoman Volkov 		CS4245_A_OUT_SEL_DAC | CS4245_ASYNCH;
1313c1611ddSRoman Volkov 	data->cs4245_shadow[CS4245_DAC_CTRL_1] =
1323c1611ddSRoman Volkov 		CS4245_DAC_FM_SINGLE | CS4245_DAC_DIF_LJUST;
1333c1611ddSRoman Volkov 	data->cs4245_shadow[CS4245_DAC_CTRL_2] =
1343c1611ddSRoman Volkov 		CS4245_DAC_SOFT | CS4245_DAC_ZERO | CS4245_INVERT_DAC;
1353c1611ddSRoman Volkov 	data->cs4245_shadow[CS4245_ADC_CTRL] =
1363c1611ddSRoman Volkov 		CS4245_ADC_FM_SINGLE | CS4245_ADC_DIF_LJUST;
1373c1611ddSRoman Volkov 	data->cs4245_shadow[CS4245_ANALOG_IN] =
1383c1611ddSRoman Volkov 		CS4245_PGA_SOFT | CS4245_PGA_ZERO;
1393c1611ddSRoman Volkov 	data->cs4245_shadow[CS4245_PGA_B_CTRL] = 0;
1403c1611ddSRoman Volkov 	data->cs4245_shadow[CS4245_PGA_A_CTRL] = 0;
1413f49a66fSRoman Volkov 	data->cs4245_shadow[CS4245_DAC_A_CTRL] = 8;
1423f49a66fSRoman Volkov 	data->cs4245_shadow[CS4245_DAC_B_CTRL] = 8;
1433c1611ddSRoman Volkov 
1443c1611ddSRoman Volkov 	cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW);
1453c1611ddSRoman Volkov 	snd_component_add(chip->card, "CS4245");
14666410bfdSClemens Ladisch }
14766410bfdSClemens Ladisch 
dg_init(struct oxygen * chip)148041f26b6SRoman Volkov void dg_init(struct oxygen *chip)
14966410bfdSClemens Ladisch {
15066410bfdSClemens Ladisch 	struct dg *data = chip->model_data;
15166410bfdSClemens Ladisch 
1523f49a66fSRoman Volkov 	data->output_sel = PLAYBACK_DST_HP_FP;
1533f49a66fSRoman Volkov 	data->input_sel = CAPTURE_SRC_MIC;
15466410bfdSClemens Ladisch 
15566410bfdSClemens Ladisch 	cs4245_init(chip);
1563c1611ddSRoman Volkov 	oxygen_write16(chip, OXYGEN_GPIO_CONTROL,
1573c1611ddSRoman Volkov 		       GPIO_OUTPUT_ENABLE | GPIO_HP_REAR | GPIO_INPUT_ROUTE);
1583f49a66fSRoman Volkov 	/* anti-pop delay, wait some time before enabling the output */
1593f49a66fSRoman Volkov 	msleep(2500);
1603c1611ddSRoman Volkov 	oxygen_write16(chip, OXYGEN_GPIO_DATA,
1613c1611ddSRoman Volkov 		       GPIO_OUTPUT_ENABLE | GPIO_INPUT_ROUTE);
16266410bfdSClemens Ladisch }
16366410bfdSClemens Ladisch 
dg_cleanup(struct oxygen * chip)164041f26b6SRoman Volkov void dg_cleanup(struct oxygen *chip)
16566410bfdSClemens Ladisch {
16666410bfdSClemens Ladisch 	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE);
16766410bfdSClemens Ladisch }
16866410bfdSClemens Ladisch 
dg_suspend(struct oxygen * chip)169041f26b6SRoman Volkov void dg_suspend(struct oxygen *chip)
17066410bfdSClemens Ladisch {
17166410bfdSClemens Ladisch 	dg_cleanup(chip);
17266410bfdSClemens Ladisch }
17366410bfdSClemens Ladisch 
dg_resume(struct oxygen * chip)174041f26b6SRoman Volkov void dg_resume(struct oxygen *chip)
17566410bfdSClemens Ladisch {
1763c1611ddSRoman Volkov 	cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW);
1773c1611ddSRoman Volkov 	msleep(2500);
1783c1611ddSRoman Volkov 	oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE);
17966410bfdSClemens Ladisch }
18066410bfdSClemens Ladisch 
set_cs4245_dac_params(struct oxygen * chip,struct snd_pcm_hw_params * params)181041f26b6SRoman Volkov void set_cs4245_dac_params(struct oxygen *chip,
18266410bfdSClemens Ladisch 				  struct snd_pcm_hw_params *params)
18366410bfdSClemens Ladisch {
18466410bfdSClemens Ladisch 	struct dg *data = chip->model_data;
185fddc106bSRoman Volkov 	unsigned char dac_ctrl;
186fddc106bSRoman Volkov 	unsigned char mclk_freq;
18766410bfdSClemens Ladisch 
188fddc106bSRoman Volkov 	dac_ctrl = data->cs4245_shadow[CS4245_DAC_CTRL_1] & ~CS4245_DAC_FM_MASK;
189fddc106bSRoman Volkov 	mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK1_MASK;
190fddc106bSRoman Volkov 	if (params_rate(params) <= 50000) {
191fddc106bSRoman Volkov 		dac_ctrl |= CS4245_DAC_FM_SINGLE;
192fddc106bSRoman Volkov 		mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT;
193fddc106bSRoman Volkov 	} else if (params_rate(params) <= 100000) {
194fddc106bSRoman Volkov 		dac_ctrl |= CS4245_DAC_FM_DOUBLE;
195fddc106bSRoman Volkov 		mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT;
196fddc106bSRoman Volkov 	} else {
197fddc106bSRoman Volkov 		dac_ctrl |= CS4245_DAC_FM_QUAD;
198fddc106bSRoman Volkov 		mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK1_SHIFT;
199fddc106bSRoman Volkov 	}
200fddc106bSRoman Volkov 	data->cs4245_shadow[CS4245_DAC_CTRL_1] = dac_ctrl;
201fddc106bSRoman Volkov 	data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq;
202fddc106bSRoman Volkov 	cs4245_write_spi(chip, CS4245_DAC_CTRL_1);
203fddc106bSRoman Volkov 	cs4245_write_spi(chip, CS4245_MCLK_FREQ);
20466410bfdSClemens Ladisch }
20566410bfdSClemens Ladisch 
set_cs4245_adc_params(struct oxygen * chip,struct snd_pcm_hw_params * params)206041f26b6SRoman Volkov void set_cs4245_adc_params(struct oxygen *chip,
20766410bfdSClemens Ladisch 				  struct snd_pcm_hw_params *params)
20866410bfdSClemens Ladisch {
20966410bfdSClemens Ladisch 	struct dg *data = chip->model_data;
210fddc106bSRoman Volkov 	unsigned char adc_ctrl;
211fddc106bSRoman Volkov 	unsigned char mclk_freq;
21266410bfdSClemens Ladisch 
213fddc106bSRoman Volkov 	adc_ctrl = data->cs4245_shadow[CS4245_ADC_CTRL] & ~CS4245_ADC_FM_MASK;
214fddc106bSRoman Volkov 	mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK2_MASK;
215fddc106bSRoman Volkov 	if (params_rate(params) <= 50000) {
216fddc106bSRoman Volkov 		adc_ctrl |= CS4245_ADC_FM_SINGLE;
217fddc106bSRoman Volkov 		mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT;
218fddc106bSRoman Volkov 	} else if (params_rate(params) <= 100000) {
219fddc106bSRoman Volkov 		adc_ctrl |= CS4245_ADC_FM_DOUBLE;
220fddc106bSRoman Volkov 		mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT;
221fddc106bSRoman Volkov 	} else {
222fddc106bSRoman Volkov 		adc_ctrl |= CS4245_ADC_FM_QUAD;
223fddc106bSRoman Volkov 		mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK2_SHIFT;
224fddc106bSRoman Volkov 	}
225fddc106bSRoman Volkov 	data->cs4245_shadow[CS4245_ADC_CTRL] = adc_ctrl;
226fddc106bSRoman Volkov 	data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq;
227fddc106bSRoman Volkov 	cs4245_write_spi(chip, CS4245_ADC_CTRL);
228fddc106bSRoman Volkov 	cs4245_write_spi(chip, CS4245_MCLK_FREQ);
22966410bfdSClemens Ladisch }
23066410bfdSClemens Ladisch 
shift_bits(unsigned int value,unsigned int shift_from,unsigned int shift_to,unsigned int mask)23130556441SClemens Ladisch static inline unsigned int shift_bits(unsigned int value,
23230556441SClemens Ladisch 				      unsigned int shift_from,
23330556441SClemens Ladisch 				      unsigned int shift_to,
23430556441SClemens Ladisch 				      unsigned int mask)
23530556441SClemens Ladisch {
23630556441SClemens Ladisch 	if (shift_from < shift_to)
23730556441SClemens Ladisch 		return (value << (shift_to - shift_from)) & mask;
23830556441SClemens Ladisch 	else
23930556441SClemens Ladisch 		return (value >> (shift_from - shift_to)) & mask;
24030556441SClemens Ladisch }
24130556441SClemens Ladisch 
adjust_dg_dac_routing(struct oxygen * chip,unsigned int play_routing)242041f26b6SRoman Volkov unsigned int adjust_dg_dac_routing(struct oxygen *chip,
243efbeb071SClemens Ladisch 					  unsigned int play_routing)
244efbeb071SClemens Ladisch {
2451f91ecc1SRoman Volkov 	struct dg *data = chip->model_data;
2461f91ecc1SRoman Volkov 
2472809cb84SRoman Volkov 	switch (data->output_sel) {
2481f91ecc1SRoman Volkov 	case PLAYBACK_DST_HP:
2491f91ecc1SRoman Volkov 	case PLAYBACK_DST_HP_FP:
2501f91ecc1SRoman Volkov 		oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING,
2511f91ecc1SRoman Volkov 			OXYGEN_PLAY_MUTE23 | OXYGEN_PLAY_MUTE45 |
2521f91ecc1SRoman Volkov 			OXYGEN_PLAY_MUTE67, OXYGEN_PLAY_MUTE_MASK);
2531f91ecc1SRoman Volkov 		break;
2541f91ecc1SRoman Volkov 	case PLAYBACK_DST_MULTICH:
2551f91ecc1SRoman Volkov 		oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING,
2561f91ecc1SRoman Volkov 			OXYGEN_PLAY_MUTE01, OXYGEN_PLAY_MUTE_MASK);
2571f91ecc1SRoman Volkov 		break;
2581f91ecc1SRoman Volkov 	}
25930556441SClemens Ladisch 	return (play_routing & OXYGEN_PLAY_DAC0_SOURCE_MASK) |
26030556441SClemens Ladisch 	       shift_bits(play_routing,
26130556441SClemens Ladisch 			  OXYGEN_PLAY_DAC2_SOURCE_SHIFT,
26230556441SClemens Ladisch 			  OXYGEN_PLAY_DAC1_SOURCE_SHIFT,
26330556441SClemens Ladisch 			  OXYGEN_PLAY_DAC1_SOURCE_MASK) |
26430556441SClemens Ladisch 	       shift_bits(play_routing,
26530556441SClemens Ladisch 			  OXYGEN_PLAY_DAC1_SOURCE_SHIFT,
26630556441SClemens Ladisch 			  OXYGEN_PLAY_DAC2_SOURCE_SHIFT,
26730556441SClemens Ladisch 			  OXYGEN_PLAY_DAC2_SOURCE_MASK) |
26830556441SClemens Ladisch 	       shift_bits(play_routing,
26930556441SClemens Ladisch 			  OXYGEN_PLAY_DAC0_SOURCE_SHIFT,
27030556441SClemens Ladisch 			  OXYGEN_PLAY_DAC3_SOURCE_SHIFT,
27130556441SClemens Ladisch 			  OXYGEN_PLAY_DAC3_SOURCE_MASK);
272efbeb071SClemens Ladisch }
273efbeb071SClemens Ladisch 
dump_cs4245_registers(struct oxygen * chip,struct snd_info_buffer * buffer)274041f26b6SRoman Volkov void dump_cs4245_registers(struct oxygen *chip,
27566410bfdSClemens Ladisch 				  struct snd_info_buffer *buffer)
27666410bfdSClemens Ladisch {
27766410bfdSClemens Ladisch 	struct dg *data = chip->model_data;
27806f70d0dSRoman Volkov 	unsigned int addr;
27966410bfdSClemens Ladisch 
28066410bfdSClemens Ladisch 	snd_iprintf(buffer, "\nCS4245:");
28106f70d0dSRoman Volkov 	cs4245_read_spi(chip, CS4245_INT_STATUS);
28206f70d0dSRoman Volkov 	for (addr = 1; addr < ARRAY_SIZE(data->cs4245_shadow); addr++)
28306f70d0dSRoman Volkov 		snd_iprintf(buffer, " %02x", data->cs4245_shadow[addr]);
28466410bfdSClemens Ladisch 	snd_iprintf(buffer, "\n");
28566410bfdSClemens Ladisch }
286