2 * card driver for the Xonar DG/DGX
4 * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
5 * Copyright (c) Roman Volkov <v1ron@mail.ru>
7 * This driver is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License, version 2.
10 * This driver is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this driver; if not, see <http://www.gnu.org/licenses/>.
23 * CS4245 and CS4361 both will mute all outputs if any clock ratio
32 * I²S 2 -> CS4361 (center/LFE)
33 * I²S 3 -> CS4361 (surround)
34 * I²S 4 -> CS4361 (front)
39 * GPIO 4 <- headphone detect
40 * GPIO 5 -> enable ADC analog circuit for the left channel
41 * GPIO 6 -> enable ADC analog circuit for the right channel
42 * GPIO 7 -> switch green rear output jack between CS4245 and and the first
43 * channel of CS4361 (mechanical relay)
44 * GPIO 8 -> enable output to speakers
50 * input 2 <- front mic
52 * DAC out -> headphones
53 * aux out -> front panel headphones
56 #include <linux/pci.h>
57 #include <linux/delay.h>
58 #include <sound/control.h>
59 #include <sound/core.h>
60 #include <sound/info.h>
61 #include <sound/pcm.h>
62 #include <sound/tlv.h>
67 int cs4245_write_spi(struct oxygen
*chip
, u8 reg
)
69 struct dg
*data
= chip
->model_data
;
73 packet
|= (CS4245_SPI_ADDRESS
| CS4245_SPI_WRITE
) << 16;
74 packet
|= data
->cs4245_shadow
[reg
];
76 return oxygen_write_spi(chip
, OXYGEN_SPI_TRIGGER
|
77 OXYGEN_SPI_DATA_LENGTH_3
|
78 OXYGEN_SPI_CLOCK_1280
|
79 (0 << OXYGEN_SPI_CODEC_SHIFT
) |
80 OXYGEN_SPI_CEN_LATCH_CLOCK_HI
,
84 int cs4245_read_spi(struct oxygen
*chip
, u8 addr
)
86 struct dg
*data
= chip
->model_data
;
89 ret
= oxygen_write_spi(chip
, OXYGEN_SPI_TRIGGER
|
90 OXYGEN_SPI_DATA_LENGTH_2
|
91 OXYGEN_SPI_CEN_LATCH_CLOCK_HI
|
92 OXYGEN_SPI_CLOCK_1280
| (0 << OXYGEN_SPI_CODEC_SHIFT
),
93 ((CS4245_SPI_ADDRESS
| CS4245_SPI_WRITE
) << 8) | addr
);
97 ret
= oxygen_write_spi(chip
, OXYGEN_SPI_TRIGGER
|
98 OXYGEN_SPI_DATA_LENGTH_2
|
99 OXYGEN_SPI_CEN_LATCH_CLOCK_HI
|
100 OXYGEN_SPI_CLOCK_1280
| (0 << OXYGEN_SPI_CODEC_SHIFT
),
101 (CS4245_SPI_ADDRESS
| CS4245_SPI_READ
) << 8);
105 data
->cs4245_shadow
[addr
] = oxygen_read8(chip
, OXYGEN_SPI_DATA1
);
110 int cs4245_shadow_control(struct oxygen
*chip
, enum cs4245_shadow_operation op
)
112 struct dg
*data
= chip
->model_data
;
116 for (addr
= 1; addr
< ARRAY_SIZE(data
->cs4245_shadow
); addr
++) {
117 ret
= (op
== CS4245_SAVE_TO_SHADOW
?
118 cs4245_read_spi(chip
, addr
) :
119 cs4245_write_spi(chip
, addr
));
126 static void cs4245_init(struct oxygen
*chip
)
128 struct dg
*data
= chip
->model_data
;
130 /* save the initial state: codec version, registers */
131 cs4245_shadow_control(chip
, CS4245_SAVE_TO_SHADOW
);
134 * Power up the CODEC internals, enable soft ramp & zero cross, work in
135 * async. mode, enable aux output from DAC. Invert DAC output as in the
138 data
->cs4245_shadow
[CS4245_POWER_CTRL
] = 0;
139 data
->cs4245_shadow
[CS4245_SIGNAL_SEL
] =
140 CS4245_A_OUT_SEL_DAC
| CS4245_ASYNCH
;
141 data
->cs4245_shadow
[CS4245_DAC_CTRL_1
] =
142 CS4245_DAC_FM_SINGLE
| CS4245_DAC_DIF_LJUST
;
143 data
->cs4245_shadow
[CS4245_DAC_CTRL_2
] =
144 CS4245_DAC_SOFT
| CS4245_DAC_ZERO
| CS4245_INVERT_DAC
;
145 data
->cs4245_shadow
[CS4245_ADC_CTRL
] =
146 CS4245_ADC_FM_SINGLE
| CS4245_ADC_DIF_LJUST
;
147 data
->cs4245_shadow
[CS4245_ANALOG_IN
] =
148 CS4245_PGA_SOFT
| CS4245_PGA_ZERO
;
149 data
->cs4245_shadow
[CS4245_PGA_B_CTRL
] = 0;
150 data
->cs4245_shadow
[CS4245_PGA_A_CTRL
] = 0;
151 data
->cs4245_shadow
[CS4245_DAC_A_CTRL
] = 8;
152 data
->cs4245_shadow
[CS4245_DAC_B_CTRL
] = 8;
154 cs4245_shadow_control(chip
, CS4245_LOAD_FROM_SHADOW
);
155 snd_component_add(chip
->card
, "CS4245");
158 void dg_init(struct oxygen
*chip
)
160 struct dg
*data
= chip
->model_data
;
162 data
->output_sel
= PLAYBACK_DST_HP_FP
;
163 data
->input_sel
= CAPTURE_SRC_MIC
;
166 oxygen_write16(chip
, OXYGEN_GPIO_CONTROL
,
167 GPIO_OUTPUT_ENABLE
| GPIO_HP_REAR
| GPIO_INPUT_ROUTE
);
168 /* anti-pop delay, wait some time before enabling the output */
170 oxygen_write16(chip
, OXYGEN_GPIO_DATA
,
171 GPIO_OUTPUT_ENABLE
| GPIO_INPUT_ROUTE
);
174 void dg_cleanup(struct oxygen
*chip
)
176 oxygen_clear_bits16(chip
, OXYGEN_GPIO_DATA
, GPIO_OUTPUT_ENABLE
);
179 void dg_suspend(struct oxygen
*chip
)
184 void dg_resume(struct oxygen
*chip
)
186 cs4245_shadow_control(chip
, CS4245_LOAD_FROM_SHADOW
);
188 oxygen_set_bits16(chip
, OXYGEN_GPIO_DATA
, GPIO_OUTPUT_ENABLE
);
191 void set_cs4245_dac_params(struct oxygen
*chip
,
192 struct snd_pcm_hw_params
*params
)
194 struct dg
*data
= chip
->model_data
;
195 unsigned char dac_ctrl
;
196 unsigned char mclk_freq
;
198 dac_ctrl
= data
->cs4245_shadow
[CS4245_DAC_CTRL_1
] & ~CS4245_DAC_FM_MASK
;
199 mclk_freq
= data
->cs4245_shadow
[CS4245_MCLK_FREQ
] & ~CS4245_MCLK1_MASK
;
200 if (params_rate(params
) <= 50000) {
201 dac_ctrl
|= CS4245_DAC_FM_SINGLE
;
202 mclk_freq
|= CS4245_MCLK_1
<< CS4245_MCLK1_SHIFT
;
203 } else if (params_rate(params
) <= 100000) {
204 dac_ctrl
|= CS4245_DAC_FM_DOUBLE
;
205 mclk_freq
|= CS4245_MCLK_1
<< CS4245_MCLK1_SHIFT
;
207 dac_ctrl
|= CS4245_DAC_FM_QUAD
;
208 mclk_freq
|= CS4245_MCLK_2
<< CS4245_MCLK1_SHIFT
;
210 data
->cs4245_shadow
[CS4245_DAC_CTRL_1
] = dac_ctrl
;
211 data
->cs4245_shadow
[CS4245_MCLK_FREQ
] = mclk_freq
;
212 cs4245_write_spi(chip
, CS4245_DAC_CTRL_1
);
213 cs4245_write_spi(chip
, CS4245_MCLK_FREQ
);
216 void set_cs4245_adc_params(struct oxygen
*chip
,
217 struct snd_pcm_hw_params
*params
)
219 struct dg
*data
= chip
->model_data
;
220 unsigned char adc_ctrl
;
221 unsigned char mclk_freq
;
223 adc_ctrl
= data
->cs4245_shadow
[CS4245_ADC_CTRL
] & ~CS4245_ADC_FM_MASK
;
224 mclk_freq
= data
->cs4245_shadow
[CS4245_MCLK_FREQ
] & ~CS4245_MCLK2_MASK
;
225 if (params_rate(params
) <= 50000) {
226 adc_ctrl
|= CS4245_ADC_FM_SINGLE
;
227 mclk_freq
|= CS4245_MCLK_1
<< CS4245_MCLK2_SHIFT
;
228 } else if (params_rate(params
) <= 100000) {
229 adc_ctrl
|= CS4245_ADC_FM_DOUBLE
;
230 mclk_freq
|= CS4245_MCLK_1
<< CS4245_MCLK2_SHIFT
;
232 adc_ctrl
|= CS4245_ADC_FM_QUAD
;
233 mclk_freq
|= CS4245_MCLK_2
<< CS4245_MCLK2_SHIFT
;
235 data
->cs4245_shadow
[CS4245_ADC_CTRL
] = adc_ctrl
;
236 data
->cs4245_shadow
[CS4245_MCLK_FREQ
] = mclk_freq
;
237 cs4245_write_spi(chip
, CS4245_ADC_CTRL
);
238 cs4245_write_spi(chip
, CS4245_MCLK_FREQ
);
241 static inline unsigned int shift_bits(unsigned int value
,
242 unsigned int shift_from
,
243 unsigned int shift_to
,
246 if (shift_from
< shift_to
)
247 return (value
<< (shift_to
- shift_from
)) & mask
;
249 return (value
>> (shift_from
- shift_to
)) & mask
;
252 unsigned int adjust_dg_dac_routing(struct oxygen
*chip
,
253 unsigned int play_routing
)
255 struct dg
*data
= chip
->model_data
;
257 switch (data
->output_sel
) {
258 case PLAYBACK_DST_HP
:
259 case PLAYBACK_DST_HP_FP
:
260 oxygen_write8_masked(chip
, OXYGEN_PLAY_ROUTING
,
261 OXYGEN_PLAY_MUTE23
| OXYGEN_PLAY_MUTE45
|
262 OXYGEN_PLAY_MUTE67
, OXYGEN_PLAY_MUTE_MASK
);
264 case PLAYBACK_DST_MULTICH
:
265 oxygen_write8_masked(chip
, OXYGEN_PLAY_ROUTING
,
266 OXYGEN_PLAY_MUTE01
, OXYGEN_PLAY_MUTE_MASK
);
269 return (play_routing
& OXYGEN_PLAY_DAC0_SOURCE_MASK
) |
270 shift_bits(play_routing
,
271 OXYGEN_PLAY_DAC2_SOURCE_SHIFT
,
272 OXYGEN_PLAY_DAC1_SOURCE_SHIFT
,
273 OXYGEN_PLAY_DAC1_SOURCE_MASK
) |
274 shift_bits(play_routing
,
275 OXYGEN_PLAY_DAC1_SOURCE_SHIFT
,
276 OXYGEN_PLAY_DAC2_SOURCE_SHIFT
,
277 OXYGEN_PLAY_DAC2_SOURCE_MASK
) |
278 shift_bits(play_routing
,
279 OXYGEN_PLAY_DAC0_SOURCE_SHIFT
,
280 OXYGEN_PLAY_DAC3_SOURCE_SHIFT
,
281 OXYGEN_PLAY_DAC3_SOURCE_MASK
);
284 void dump_cs4245_registers(struct oxygen
*chip
,
285 struct snd_info_buffer
*buffer
)
287 struct dg
*data
= chip
->model_data
;
290 snd_iprintf(buffer
, "\nCS4245:");
291 cs4245_read_spi(chip
, CS4245_INT_STATUS
);
292 for (addr
= 1; addr
< ARRAY_SIZE(data
->cs4245_shadow
); addr
++)
293 snd_iprintf(buffer
, " %02x", data
->cs4245_shadow
[addr
]);
294 snd_iprintf(buffer
, "\n");