123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- /*
- * 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");
- }
|