286 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * card driver for the Xonar DG/DGX4 *5 * Copyright (c) Clemens Ladisch <clemens@ladisch.de>6 * Copyright (c) Roman Volkov <v1ron@mail.ru>7 */8 9/*10 * Xonar DG/DGX11 * ------------12 *13 * CS4245 and CS4361 both will mute all outputs if any clock ratio14 * is invalid.15 *16 * CMI8788:17 *18 * SPI 0 -> CS424519 *20 * Playback:21 * I²S 1 -> CS424522 * I²S 2 -> CS4361 (center/LFE)23 * I²S 3 -> CS4361 (surround)24 * I²S 4 -> CS4361 (front)25 * Capture:26 * I²S ADC 1 <- CS424527 *28 * GPIO 3 <- ?29 * GPIO 4 <- headphone detect30 * GPIO 5 -> enable ADC analog circuit for the left channel31 * GPIO 6 -> enable ADC analog circuit for the right channel32 * GPIO 7 -> switch green rear output jack between CS4245 and the first33 * channel of CS4361 (mechanical relay)34 * GPIO 8 -> enable output to speakers35 *36 * CS4245:37 *38 * input 0 <- mic39 * input 1 <- aux40 * input 2 <- front mic41 * input 4 <- line42 * DAC out -> headphones43 * aux out -> front panel headphones44 */45 46#include <linux/pci.h>47#include <linux/delay.h>48#include <sound/control.h>49#include <sound/core.h>50#include <sound/info.h>51#include <sound/pcm.h>52#include <sound/tlv.h>53#include "oxygen.h"54#include "xonar_dg.h"55#include "cs4245.h"56 57int cs4245_write_spi(struct oxygen *chip, u8 reg)58{59 struct dg *data = chip->model_data;60 unsigned int packet;61 62 packet = reg << 8;63 packet |= (CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 16;64 packet |= data->cs4245_shadow[reg];65 66 return oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |67 OXYGEN_SPI_DATA_LENGTH_3 |68 OXYGEN_SPI_CLOCK_1280 |69 (0 << OXYGEN_SPI_CODEC_SHIFT) |70 OXYGEN_SPI_CEN_LATCH_CLOCK_HI,71 packet);72}73 74int cs4245_read_spi(struct oxygen *chip, u8 addr)75{76 struct dg *data = chip->model_data;77 int ret;78 79 ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |80 OXYGEN_SPI_DATA_LENGTH_2 |81 OXYGEN_SPI_CEN_LATCH_CLOCK_HI |82 OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT),83 ((CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 8) | addr);84 if (ret < 0)85 return ret;86 87 ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |88 OXYGEN_SPI_DATA_LENGTH_2 |89 OXYGEN_SPI_CEN_LATCH_CLOCK_HI |90 OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT),91 (CS4245_SPI_ADDRESS | CS4245_SPI_READ) << 8);92 if (ret < 0)93 return ret;94 95 data->cs4245_shadow[addr] = oxygen_read8(chip, OXYGEN_SPI_DATA1);96 97 return 0;98}99 100int cs4245_shadow_control(struct oxygen *chip, enum cs4245_shadow_operation op)101{102 struct dg *data = chip->model_data;103 unsigned char addr;104 int ret;105 106 for (addr = 1; addr < ARRAY_SIZE(data->cs4245_shadow); addr++) {107 ret = (op == CS4245_SAVE_TO_SHADOW ?108 cs4245_read_spi(chip, addr) :109 cs4245_write_spi(chip, addr));110 if (ret < 0)111 return ret;112 }113 return 0;114}115 116static void cs4245_init(struct oxygen *chip)117{118 struct dg *data = chip->model_data;119 120 /* save the initial state: codec version, registers */121 cs4245_shadow_control(chip, CS4245_SAVE_TO_SHADOW);122 123 /*124 * Power up the CODEC internals, enable soft ramp & zero cross, work in125 * async. mode, enable aux output from DAC. Invert DAC output as in the126 * Windows driver.127 */128 data->cs4245_shadow[CS4245_POWER_CTRL] = 0;129 data->cs4245_shadow[CS4245_SIGNAL_SEL] =130 CS4245_A_OUT_SEL_DAC | CS4245_ASYNCH;131 data->cs4245_shadow[CS4245_DAC_CTRL_1] =132 CS4245_DAC_FM_SINGLE | CS4245_DAC_DIF_LJUST;133 data->cs4245_shadow[CS4245_DAC_CTRL_2] =134 CS4245_DAC_SOFT | CS4245_DAC_ZERO | CS4245_INVERT_DAC;135 data->cs4245_shadow[CS4245_ADC_CTRL] =136 CS4245_ADC_FM_SINGLE | CS4245_ADC_DIF_LJUST;137 data->cs4245_shadow[CS4245_ANALOG_IN] =138 CS4245_PGA_SOFT | CS4245_PGA_ZERO;139 data->cs4245_shadow[CS4245_PGA_B_CTRL] = 0;140 data->cs4245_shadow[CS4245_PGA_A_CTRL] = 0;141 data->cs4245_shadow[CS4245_DAC_A_CTRL] = 8;142 data->cs4245_shadow[CS4245_DAC_B_CTRL] = 8;143 144 cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW);145 snd_component_add(chip->card, "CS4245");146}147 148void dg_init(struct oxygen *chip)149{150 struct dg *data = chip->model_data;151 152 data->output_sel = PLAYBACK_DST_HP_FP;153 data->input_sel = CAPTURE_SRC_MIC;154 155 cs4245_init(chip);156 oxygen_write16(chip, OXYGEN_GPIO_CONTROL,157 GPIO_OUTPUT_ENABLE | GPIO_HP_REAR | GPIO_INPUT_ROUTE);158 /* anti-pop delay, wait some time before enabling the output */159 msleep(2500);160 oxygen_write16(chip, OXYGEN_GPIO_DATA,161 GPIO_OUTPUT_ENABLE | GPIO_INPUT_ROUTE);162}163 164void dg_cleanup(struct oxygen *chip)165{166 oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE);167}168 169void dg_suspend(struct oxygen *chip)170{171 dg_cleanup(chip);172}173 174void dg_resume(struct oxygen *chip)175{176 cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW);177 msleep(2500);178 oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE);179}180 181void set_cs4245_dac_params(struct oxygen *chip,182 struct snd_pcm_hw_params *params)183{184 struct dg *data = chip->model_data;185 unsigned char dac_ctrl;186 unsigned char mclk_freq;187 188 dac_ctrl = data->cs4245_shadow[CS4245_DAC_CTRL_1] & ~CS4245_DAC_FM_MASK;189 mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK1_MASK;190 if (params_rate(params) <= 50000) {191 dac_ctrl |= CS4245_DAC_FM_SINGLE;192 mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT;193 } else if (params_rate(params) <= 100000) {194 dac_ctrl |= CS4245_DAC_FM_DOUBLE;195 mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT;196 } else {197 dac_ctrl |= CS4245_DAC_FM_QUAD;198 mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK1_SHIFT;199 }200 data->cs4245_shadow[CS4245_DAC_CTRL_1] = dac_ctrl;201 data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq;202 cs4245_write_spi(chip, CS4245_DAC_CTRL_1);203 cs4245_write_spi(chip, CS4245_MCLK_FREQ);204}205 206void set_cs4245_adc_params(struct oxygen *chip,207 struct snd_pcm_hw_params *params)208{209 struct dg *data = chip->model_data;210 unsigned char adc_ctrl;211 unsigned char mclk_freq;212 213 adc_ctrl = data->cs4245_shadow[CS4245_ADC_CTRL] & ~CS4245_ADC_FM_MASK;214 mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK2_MASK;215 if (params_rate(params) <= 50000) {216 adc_ctrl |= CS4245_ADC_FM_SINGLE;217 mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT;218 } else if (params_rate(params) <= 100000) {219 adc_ctrl |= CS4245_ADC_FM_DOUBLE;220 mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT;221 } else {222 adc_ctrl |= CS4245_ADC_FM_QUAD;223 mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK2_SHIFT;224 }225 data->cs4245_shadow[CS4245_ADC_CTRL] = adc_ctrl;226 data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq;227 cs4245_write_spi(chip, CS4245_ADC_CTRL);228 cs4245_write_spi(chip, CS4245_MCLK_FREQ);229}230 231static inline unsigned int shift_bits(unsigned int value,232 unsigned int shift_from,233 unsigned int shift_to,234 unsigned int mask)235{236 if (shift_from < shift_to)237 return (value << (shift_to - shift_from)) & mask;238 else239 return (value >> (shift_from - shift_to)) & mask;240}241 242unsigned int adjust_dg_dac_routing(struct oxygen *chip,243 unsigned int play_routing)244{245 struct dg *data = chip->model_data;246 247 switch (data->output_sel) {248 case PLAYBACK_DST_HP:249 case PLAYBACK_DST_HP_FP:250 oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING,251 OXYGEN_PLAY_MUTE23 | OXYGEN_PLAY_MUTE45 |252 OXYGEN_PLAY_MUTE67, OXYGEN_PLAY_MUTE_MASK);253 break;254 case PLAYBACK_DST_MULTICH:255 oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING,256 OXYGEN_PLAY_MUTE01, OXYGEN_PLAY_MUTE_MASK);257 break;258 }259 return (play_routing & OXYGEN_PLAY_DAC0_SOURCE_MASK) |260 shift_bits(play_routing,261 OXYGEN_PLAY_DAC2_SOURCE_SHIFT,262 OXYGEN_PLAY_DAC1_SOURCE_SHIFT,263 OXYGEN_PLAY_DAC1_SOURCE_MASK) |264 shift_bits(play_routing,265 OXYGEN_PLAY_DAC1_SOURCE_SHIFT,266 OXYGEN_PLAY_DAC2_SOURCE_SHIFT,267 OXYGEN_PLAY_DAC2_SOURCE_MASK) |268 shift_bits(play_routing,269 OXYGEN_PLAY_DAC0_SOURCE_SHIFT,270 OXYGEN_PLAY_DAC3_SOURCE_SHIFT,271 OXYGEN_PLAY_DAC3_SOURCE_MASK);272}273 274void dump_cs4245_registers(struct oxygen *chip,275 struct snd_info_buffer *buffer)276{277 struct dg *data = chip->model_data;278 unsigned int addr;279 280 snd_iprintf(buffer, "\nCS4245:");281 cs4245_read_spi(chip, CS4245_INT_STATUS);282 for (addr = 1; addr < ARRAY_SIZE(data->cs4245_shadow); addr++)283 snd_iprintf(buffer, " %02x", data->cs4245_shadow[addr]);284 snd_iprintf(buffer, "\n");285}286