[alsa-devel] [PATCH 2/2] snd-oxygen: Fixes for the Xonar DG card
Fixes for the Xonar DG model of the Oxygen driver: CS4245 CODEC now fully supported with the headphone amplifier. Both front panel and rear jacks work. Full support of capturing: from the microphone, line in, and aux input. Multichannel playback routing corrected for 2.0/4.0/5.1 speakers New mixer controls, hardware volume control supported for headphones only.
Signed-off-by: Roman I Volkov v1ron@mail.ru --- Depends on: [PATCH 1/2] snd-oxygen: Changes for oxygen driver to fix the Xonar DG card diff -uprN linux-3.12/sound/pci/oxygen/Makefile linux-3.12-my/sound/pci/oxygen/Makefile --- linux-3.12/sound/pci/oxygen/Makefile 2013-11-04 03:41:51.000000000 +0400 +++ linux-3.12-my/sound/pci/oxygen/Makefile 2013-11-15 16:55:55.000000000 +0400 @@ -1,5 +1,5 @@ snd-oxygen-lib-objs := oxygen_io.o oxygen_lib.o oxygen_mixer.o oxygen_pcm.o -snd-oxygen-objs := oxygen.o xonar_dg.o +snd-oxygen-objs := oxygen.o xonar_dg.o xonar_dg_mixer.o snd-virtuoso-objs := virtuoso.o xonar_lib.o \ xonar_pcm179x.o xonar_cs43xx.o xonar_wm87x6.o xonar_hdmi.o
diff -uprN linux-3.12/sound/pci/oxygen/xonar_dg.c linux-3.12-my/sound/pci/oxygen/xonar_dg.c --- linux-3.12/sound/pci/oxygen/xonar_dg.c 2013-11-04 03:41:51.000000000 +0400 +++ linux-3.12-my/sound/pci/oxygen/xonar_dg.c 2013-11-19 01:01:07.000000000 +0400 @@ -2,7 +2,7 @@ * 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. @@ -17,32 +17,48 @@ */
/* - * Xonar DG/DGX + * TODOs: + * DGX card: not tested. + * Suspend/resume: not tested + * SPDIF: not tested + * 128kHz doesn't work for CS4361 (multichannel playback) + * * ------------ * + * Pecularities: + * CS4245/CS4361 both will mute outputs if MCLK\BCLK\LRCK rates are invalid. + * CS4245 mutes DAC even if ADC clocks are invalid. + * CS4245 Auxiliary Output is connected to the front panel headphones jack + * through amplifier. + * * 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 3 <- unused (?) * GPIO 4 <- headphone detect - * GPIO 5 -> route input jack to line-in (0) or mic-in (1) - * GPIO 6 -> route input jack to line-in (0) or mic-in (1) - * GPIO 7 -> enable rear headphone amp - * GPIO 8 -> enable output to speakers + * GPIO 5 -> ADC analog circuit power supply control for left channel (?) + * GPIO 6 -> ADC analog circuit power supply control for right channel (?) + * GPIO 7 -> switches green rear output jack between CS4245 / CS4361 first + * channel + * GPIO 8 -> DAC analog circuit power supply control (?) * * CS4245: * - * input 1 <- aux - * input 2 <- front mic - * input 4 <- line/mic + * input 0 <- Microphone + * input 1 <- Aux + * input 2 <- Front Panel Mic + * input 4 <- Line In * DAC out -> headphones - * aux out -> front panel headphones + * AUX out -> front panel headphones */
#include <linux/pci.h> @@ -56,536 +72,263 @@ #include "xonar_dg.h" #include "cs4245.h"
-#define GPIO_MAGIC 0x0008 -#define GPIO_HP_DETECT 0x0010 -#define GPIO_INPUT_ROUTE 0x0060 -#define GPIO_HP_REAR 0x0080 -#define GPIO_OUTPUT_ENABLE 0x0100 - -struct dg { - unsigned int output_sel; - s8 input_vol[4][2]; - unsigned int input_sel; - u8 hp_vol_att; - u8 cs4245_regs[0x11]; -}; - -static void cs4245_write(struct oxygen *chip, unsigned int reg, u8 value) +int cs4245_write_spi(struct oxygen *chip, u8 reg) { struct dg *data = chip->model_data;
- 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, - CS4245_SPI_ADDRESS | - CS4245_SPI_WRITE | - (reg << 8) | value); - data->cs4245_regs[reg] = value; + 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, + (((u32)CS4245_SPI_ADDRESS) << 16) | (((u32)reg) << 8) | + ((u32)data->cs4245_shadow[reg])); }
-static void cs4245_write_cached(struct oxygen *chip, unsigned int reg, u8 value) +int cs4245_read_spi(struct oxygen *chip, u8 reg) { struct dg *data = chip->model_data; + int ret;
- if (value != data->cs4245_regs[reg]) - cs4245_write(chip, reg, value); -} - -static void cs4245_registers_init(struct oxygen *chip) -{ - struct dg *data = chip->model_data; + 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 << 8) | reg); + if (ret < 0) + return ret;
- cs4245_write(chip, CS4245_POWER_CTRL, CS4245_PDN); - cs4245_write(chip, CS4245_DAC_CTRL_1, - data->cs4245_regs[CS4245_DAC_CTRL_1]); - cs4245_write(chip, CS4245_ADC_CTRL, - data->cs4245_regs[CS4245_ADC_CTRL]); - cs4245_write(chip, CS4245_SIGNAL_SEL, - data->cs4245_regs[CS4245_SIGNAL_SEL]); - cs4245_write(chip, CS4245_PGA_B_CTRL, - data->cs4245_regs[CS4245_PGA_B_CTRL]); - cs4245_write(chip, CS4245_PGA_A_CTRL, - data->cs4245_regs[CS4245_PGA_A_CTRL]); - cs4245_write(chip, CS4245_ANALOG_IN, - data->cs4245_regs[CS4245_ANALOG_IN]); - cs4245_write(chip, CS4245_DAC_A_CTRL, - data->cs4245_regs[CS4245_DAC_A_CTRL]); - cs4245_write(chip, CS4245_DAC_B_CTRL, - data->cs4245_regs[CS4245_DAC_B_CTRL]); - cs4245_write(chip, CS4245_DAC_CTRL_2, - CS4245_DAC_SOFT | CS4245_DAC_ZERO | CS4245_INVERT_DAC); - cs4245_write(chip, CS4245_INT_MASK, 0); - cs4245_write(chip, CS4245_POWER_CTRL, 0); -} + 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; + ret = oxygen_wait_spi(chip); + if (ret < 0) + return ret;
-static void cs4245_init(struct oxygen *chip) -{ - struct dg *data = chip->model_data; + data->cs4245_shadow[reg] = oxygen_read8(chip, OXYGEN_SPI_DATA1);
- data->cs4245_regs[CS4245_DAC_CTRL_1] = - CS4245_DAC_FM_SINGLE | CS4245_DAC_DIF_LJUST; - data->cs4245_regs[CS4245_ADC_CTRL] = - CS4245_ADC_FM_SINGLE | CS4245_ADC_DIF_LJUST; - data->cs4245_regs[CS4245_SIGNAL_SEL] = - CS4245_A_OUT_SEL_HIZ | CS4245_ASYNCH; - data->cs4245_regs[CS4245_PGA_B_CTRL] = 0; - data->cs4245_regs[CS4245_PGA_A_CTRL] = 0; - data->cs4245_regs[CS4245_ANALOG_IN] = - CS4245_PGA_SOFT | CS4245_PGA_ZERO | CS4245_SEL_INPUT_4; - data->cs4245_regs[CS4245_DAC_A_CTRL] = 0; - data->cs4245_regs[CS4245_DAC_B_CTRL] = 0; - cs4245_registers_init(chip); - snd_component_add(chip->card, "CS4245"); + return 0; }
-static void dg_output_enable(struct oxygen *chip) +static int cs4245_dump_regs(struct oxygen *chip, bool bRead) { - msleep(2500); - oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE); + u8 address; + + for (address = 0; address <= 0x10; address++) { + return bRead ? cs4245_read_spi(chip, address) : + cs4245_write_spi(chip, address); + } + return 0; }
-static void dg_init(struct oxygen *chip) +static void cs4245_init(struct oxygen *chip) { struct dg *data = chip->model_data;
- data->output_sel = 0; - data->input_sel = 3; - data->hp_vol_att = 2 * 16; - - cs4245_init(chip); + /* save the initial state */ + cs4245_dump_regs(chip, true);
- oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, - GPIO_MAGIC | GPIO_HP_DETECT); - oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, - GPIO_INPUT_ROUTE | GPIO_HP_REAR | GPIO_OUTPUT_ENABLE); - oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, - GPIO_INPUT_ROUTE | GPIO_HP_REAR); - dg_output_enable(chip); -} + /* + * Power up the CODEC internals, enable soft ramp & zero cross, work + * in async. mode, + * Enable aux output from DAC. DAC output in the Windows driver is + * inverted. + */ + 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] = 0; + data->cs4245_shadow[CS4245_DAC_CTRL_2] = CS4245_DAC_SOFT | + CS4245_DAC_ZERO | CS4245_INVERT_DAC; + data->cs4245_shadow[CS4245_ADC_CTRL] = 0; + data->cs4245_shadow[CS4245_ANALOG_IN] = CS4245_PGA_SOFT | + CS4245_PGA_ZERO; + data->cs4245_shadow[CS4245_MCLK_FREQ] = (CS4245_MCLK_1 << + CS4245_MCLK1_SHIFT) | (CS4245_MCLK_1 << CS4245_MCLK2_SHIFT); + + cs4245_write_spi(chip, CS4245_SIGNAL_SEL); + cs4245_write_spi(chip, CS4245_DAC_CTRL_1); + cs4245_write_spi(chip, CS4245_DAC_CTRL_2); + cs4245_write_spi(chip, CS4245_ADC_CTRL); + cs4245_write_spi(chip, CS4245_ANALOG_IN); + cs4245_write_spi(chip, CS4245_MCLK_FREQ); + cs4245_write_spi(chip, CS4245_POWER_CTRL);
-static void dg_cleanup(struct oxygen *chip) -{ - oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE); + snd_component_add(chip->card, "CS4245"); }
-static void dg_suspend(struct oxygen *chip) +static void dg_init(struct oxygen *chip) { - dg_cleanup(chip); -} + /* + * Recommended sequence from the datasheet: reset the codec and then + * initialize it. + * The pin XGPIO1 as XGPIO1, gpios 8,7,6,5,1,0 as outputs. + */ + oxygen_write8_masked(chip, OXYGEN_FUNCTION, 0, + OXYGEN_FUNCTION_RESET_CODEC); + mdelay(1); + oxygen_write8_masked(chip, OXYGEN_FUNCTION, 0xff, + OXYGEN_FUNCTION_RESET_CODEC); + mdelay(1);
-static void dg_resume(struct oxygen *chip) -{ - cs4245_registers_init(chip); - dg_output_enable(chip); + cs4245_init(chip); + oxygen_write16(chip, OXYGEN_GPIO_CONTROL, GPIO_OUTPUT_ENABLE | + GPIO_HP_REAR | GPIO_INPUT_ROUTE); + oxygen_write16(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR | GPIO_INPUT_ROUTE); + /* Anti-pop delay */ + msleep_interruptible(1000); + oxygen_write16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE | + GPIO_HP_REAR | GPIO_INPUT_ROUTE); }
static void set_cs4245_dac_params(struct oxygen *chip, struct snd_pcm_hw_params *params) { struct dg *data = chip->model_data; - u8 value; + unsigned char tmp; + unsigned char tmp2;
- value = data->cs4245_regs[CS4245_DAC_CTRL_1] & ~CS4245_DAC_FM_MASK; - if (params_rate(params) <= 50000) - value |= CS4245_DAC_FM_SINGLE; - else if (params_rate(params) <= 100000) - value |= CS4245_DAC_FM_DOUBLE; - else - value |= CS4245_DAC_FM_QUAD; - cs4245_write_cached(chip, CS4245_DAC_CTRL_1, value); + tmp = data->cs4245_shadow[CS4245_DAC_CTRL_1] & ~CS4245_DAC_FM_MASK; + tmp2 = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK1_MASK; + if (params_rate(params) <= 50000) { + tmp |= CS4245_DAC_FM_SINGLE; + tmp2 |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT; + } else if (params_rate(params) <= 100000) { + tmp |= CS4245_DAC_FM_DOUBLE; + tmp2 |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT; + } else { + tmp |= CS4245_DAC_FM_QUAD; + tmp2 |= CS4245_MCLK_2 << CS4245_MCLK1_SHIFT; + } + data->cs4245_shadow[CS4245_DAC_CTRL_1] = tmp; + data->cs4245_shadow[CS4245_MCLK_FREQ] = tmp2; + cs4245_write_spi(chip, CS4245_DAC_CTRL_1); + cs4245_write_spi(chip, CS4245_MCLK_FREQ); }
static void set_cs4245_adc_params(struct oxygen *chip, struct snd_pcm_hw_params *params) { struct dg *data = chip->model_data; - u8 value; + unsigned char tmp; + unsigned char tmp2;
- value = data->cs4245_regs[CS4245_ADC_CTRL] & ~CS4245_ADC_FM_MASK; - if (params_rate(params) <= 50000) - value |= CS4245_ADC_FM_SINGLE; - else if (params_rate(params) <= 100000) - value |= CS4245_ADC_FM_DOUBLE; - else - value |= CS4245_ADC_FM_QUAD; - cs4245_write_cached(chip, CS4245_ADC_CTRL, value); -} - -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; + tmp = data->cs4245_shadow[CS4245_ADC_CTRL] & ~CS4245_ADC_FM_MASK; + tmp2 = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK2_MASK; + if (params_rate(params) <= 50000) { + tmp |= CS4245_ADC_FM_SINGLE; + tmp2 |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT; + } else if (params_rate(params) <= 100000) { + tmp |= CS4245_ADC_FM_DOUBLE; + tmp2 |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT; + } else { + tmp |= CS4245_ADC_FM_QUAD; + tmp2 |= CS4245_MCLK_2 << CS4245_MCLK2_SHIFT; + } + data->cs4245_shadow[CS4245_ADC_CTRL] = tmp; + data->cs4245_shadow[CS4245_MCLK_FREQ] = tmp2; + cs4245_write_spi(chip, CS4245_ADC_CTRL); + cs4245_write_spi(chip, CS4245_MCLK_FREQ); }
static unsigned int adjust_dg_dac_routing(struct oxygen *chip, unsigned int play_routing) { - 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); -} - -static int output_switch_info(struct snd_kcontrol *ctl, - struct snd_ctl_elem_info *info) -{ - static const char *const names[3] = { - "Speakers", "Headphones", "FP Headphones" - }; - - return snd_ctl_enum_info(info, 1, 3, names); -} - -static int output_switch_get(struct snd_kcontrol *ctl, - struct snd_ctl_elem_value *value) -{ - struct oxygen *chip = ctl->private_data; - struct dg *data = chip->model_data; - - mutex_lock(&chip->mutex); - value->value.enumerated.item[0] = data->output_sel; - mutex_unlock(&chip->mutex); - return 0; -} - -static int output_switch_put(struct snd_kcontrol *ctl, - struct snd_ctl_elem_value *value) -{ - struct oxygen *chip = ctl->private_data; - struct dg *data = chip->model_data; - u8 reg; - int changed; - - if (value->value.enumerated.item[0] > 2) - return -EINVAL; - - mutex_lock(&chip->mutex); - changed = value->value.enumerated.item[0] != data->output_sel; - if (changed) { - data->output_sel = value->value.enumerated.item[0]; - - reg = data->cs4245_regs[CS4245_SIGNAL_SEL] & - ~CS4245_A_OUT_SEL_MASK; - reg |= data->output_sel == 2 ? - CS4245_A_OUT_SEL_DAC : CS4245_A_OUT_SEL_HIZ; - cs4245_write_cached(chip, CS4245_SIGNAL_SEL, reg); - - cs4245_write_cached(chip, CS4245_DAC_A_CTRL, - data->output_sel ? data->hp_vol_att : 0); - cs4245_write_cached(chip, CS4245_DAC_B_CTRL, - data->output_sel ? data->hp_vol_att : 0); - - oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, - data->output_sel == 1 ? GPIO_HP_REAR : 0, - GPIO_HP_REAR); - } - mutex_unlock(&chip->mutex); - return changed; -} - -static int hp_volume_offset_info(struct snd_kcontrol *ctl, - struct snd_ctl_elem_info *info) -{ - static const char *const names[3] = { - "< 64 ohms", "64-150 ohms", "150-300 ohms" - }; - - return snd_ctl_enum_info(info, 1, 3, names); -} - -static int hp_volume_offset_get(struct snd_kcontrol *ctl, - struct snd_ctl_elem_value *value) -{ - struct oxygen *chip = ctl->private_data; - struct dg *data = chip->model_data; - - mutex_lock(&chip->mutex); - if (data->hp_vol_att > 2 * 7) - value->value.enumerated.item[0] = 0; - else if (data->hp_vol_att > 0) - value->value.enumerated.item[0] = 1; - else - value->value.enumerated.item[0] = 2; - mutex_unlock(&chip->mutex); - return 0; -} - -static int hp_volume_offset_put(struct snd_kcontrol *ctl, - struct snd_ctl_elem_value *value) -{ - static const s8 atts[3] = { 2 * 16, 2 * 7, 0 }; - struct oxygen *chip = ctl->private_data; struct dg *data = chip->model_data; - s8 att; - int changed; + unsigned int routing = 0;
- if (value->value.enumerated.item[0] > 2) - return -EINVAL; - att = atts[value->value.enumerated.item[0]]; - mutex_lock(&chip->mutex); - changed = att != data->hp_vol_att; - if (changed) { - data->hp_vol_att = att; - if (data->output_sel) { - cs4245_write_cached(chip, CS4245_DAC_A_CTRL, att); - cs4245_write_cached(chip, CS4245_DAC_B_CTRL, att); - } + switch (data->pcm_output) { + 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_20_CH: + routing = (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) | + (2 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) | + (1 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) | + (0 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT); + oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING, + OXYGEN_PLAY_MUTE01 | OXYGEN_PLAY_MUTE23 | + OXYGEN_PLAY_MUTE45, OXYGEN_PLAY_MUTE_MASK); + break; + case PLAYBACK_DST_40_CH: + routing = (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) | + (2 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) | + (1 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) | + (0 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT); + oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING, + OXYGEN_PLAY_MUTE01 | + OXYGEN_PLAY_MUTE23, OXYGEN_PLAY_MUTE_MASK); + break; + case PLAYBACK_DST_51_CH: + routing = (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) | + (2 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) | + (1 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) | + (0 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT); + oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING, + OXYGEN_PLAY_MUTE01, OXYGEN_PLAY_MUTE_MASK); + break; } - mutex_unlock(&chip->mutex); - return changed; -} - -static int input_vol_info(struct snd_kcontrol *ctl, - struct snd_ctl_elem_info *info) -{ - info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - info->count = 2; - info->value.integer.min = 2 * -12; - info->value.integer.max = 2 * 12; - return 0; -} - -static int input_vol_get(struct snd_kcontrol *ctl, - struct snd_ctl_elem_value *value) -{ - struct oxygen *chip = ctl->private_data; - struct dg *data = chip->model_data; - unsigned int idx = ctl->private_value; - - mutex_lock(&chip->mutex); - value->value.integer.value[0] = data->input_vol[idx][0]; - value->value.integer.value[1] = data->input_vol[idx][1]; - mutex_unlock(&chip->mutex); - return 0; + return routing; }
-static int input_vol_put(struct snd_kcontrol *ctl, - struct snd_ctl_elem_value *value) +static void dump_cs4245_registers(struct oxygen *chip, + struct snd_info_buffer *buffer) { - struct oxygen *chip = ctl->private_data; struct dg *data = chip->model_data; - unsigned int idx = ctl->private_value; - int changed = 0; - - if (value->value.integer.value[0] < 2 * -12 || - value->value.integer.value[0] > 2 * 12 || - value->value.integer.value[1] < 2 * -12 || - value->value.integer.value[1] > 2 * 12) - return -EINVAL; - mutex_lock(&chip->mutex); - changed = data->input_vol[idx][0] != value->value.integer.value[0] || - data->input_vol[idx][1] != value->value.integer.value[1]; - if (changed) { - data->input_vol[idx][0] = value->value.integer.value[0]; - data->input_vol[idx][1] = value->value.integer.value[1]; - if (idx == data->input_sel) { - cs4245_write_cached(chip, CS4245_PGA_A_CTRL, - data->input_vol[idx][0]); - cs4245_write_cached(chip, CS4245_PGA_B_CTRL, - data->input_vol[idx][1]); - } - } - mutex_unlock(&chip->mutex); - return changed; -} - -static DECLARE_TLV_DB_SCALE(cs4245_pga_db_scale, -1200, 50, 0); - -static int input_sel_info(struct snd_kcontrol *ctl, - struct snd_ctl_elem_info *info) -{ - static const char *const names[4] = { - "Mic", "Aux", "Front Mic", "Line" - }; + unsigned int reg;
- return snd_ctl_enum_info(info, 1, 4, names); + snd_iprintf(buffer, "\nCS4245:"); + cs4245_read_spi(chip, CS4245_INT_STATUS); + for (reg = 0; reg < ARRAY_SIZE(data->cs4245_shadow); ++reg) + snd_iprintf(buffer, " %02x", data->cs4245_shadow[reg]); + snd_iprintf(buffer, "\n"); }
-static int input_sel_get(struct snd_kcontrol *ctl, - struct snd_ctl_elem_value *value) +/* Put the codec into low power mode, disable audio output, save registers */ +static void dg_suspend(struct oxygen *chip) { - struct oxygen *chip = ctl->private_data; struct dg *data = chip->model_data; - - mutex_lock(&chip->mutex); - value->value.enumerated.item[0] = data->input_sel; - mutex_unlock(&chip->mutex); - return 0; + data->cs4245_shadow[CS4245_POWER_CTRL] = CS4245_PDN_MIC | + CS4245_PDN_ADC | CS4245_PDN_DAC | CS4245_PDN; + cs4245_write_spi(chip, CS4245_POWER_CTRL); + cs4245_dump_regs(chip, true); + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, 0, GPIO_OUTPUT_ENABLE); }
-static int input_sel_put(struct snd_kcontrol *ctl, - struct snd_ctl_elem_value *value) +static void dg_resume(struct oxygen *chip) { - static const u8 sel_values[4] = { - CS4245_SEL_MIC, - CS4245_SEL_INPUT_1, - CS4245_SEL_INPUT_2, - CS4245_SEL_INPUT_4 - }; - struct oxygen *chip = ctl->private_data; struct dg *data = chip->model_data; - int changed; - - if (value->value.enumerated.item[0] > 3) - return -EINVAL; - - mutex_lock(&chip->mutex); - changed = value->value.enumerated.item[0] != data->input_sel; - if (changed) { - data->input_sel = value->value.enumerated.item[0]; - - cs4245_write(chip, CS4245_ANALOG_IN, - (data->cs4245_regs[CS4245_ANALOG_IN] & - ~CS4245_SEL_MASK) | - sel_values[data->input_sel]); - - cs4245_write_cached(chip, CS4245_PGA_A_CTRL, - data->input_vol[data->input_sel][0]); - cs4245_write_cached(chip, CS4245_PGA_B_CTRL, - data->input_vol[data->input_sel][1]); - - oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, - data->input_sel ? 0 : GPIO_INPUT_ROUTE, - GPIO_INPUT_ROUTE); - } - mutex_unlock(&chip->mutex); - return changed; + data->cs4245_shadow[CS4245_POWER_CTRL] = 0; + cs4245_dump_regs(chip, false); + msleep_interruptible(1000); + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, 0xffff, + GPIO_OUTPUT_ENABLE); }
-static int hpf_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info) -{ - static const char *const names[2] = { "Active", "Frozen" }; - - return snd_ctl_enum_info(info, 1, 2, names); -} - -static int hpf_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) +static void dg_cleanup(struct oxygen *chip) { - struct oxygen *chip = ctl->private_data; - struct dg *data = chip->model_data; - - value->value.enumerated.item[0] = - !!(data->cs4245_regs[CS4245_ADC_CTRL] & CS4245_HPF_FREEZE); - return 0; + dg_suspend(chip); }
-static int hpf_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) -{ - struct oxygen *chip = ctl->private_data; - struct dg *data = chip->model_data; - u8 reg; - int changed; - - mutex_lock(&chip->mutex); - reg = data->cs4245_regs[CS4245_ADC_CTRL] & ~CS4245_HPF_FREEZE; - if (value->value.enumerated.item[0]) - reg |= CS4245_HPF_FREEZE; - changed = reg != data->cs4245_regs[CS4245_ADC_CTRL]; - if (changed) - cs4245_write(chip, CS4245_ADC_CTRL, reg); - mutex_unlock(&chip->mutex); - return changed; -} - -#define INPUT_VOLUME(xname, index) { \ - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ - .name = xname, \ - .info = input_vol_info, \ - .get = input_vol_get, \ - .put = input_vol_put, \ - .tlv = { .p = cs4245_pga_db_scale }, \ - .private_value = index, \ -} -static const struct snd_kcontrol_new dg_controls[] = { - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Analog Output Playback Enum", - .info = output_switch_info, - .get = output_switch_get, - .put = output_switch_put, - }, - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Headphones Impedance Playback Enum", - .info = hp_volume_offset_info, - .get = hp_volume_offset_get, - .put = hp_volume_offset_put, - }, - INPUT_VOLUME("Mic Capture Volume", 0), - INPUT_VOLUME("Aux Capture Volume", 1), - INPUT_VOLUME("Front Mic Capture Volume", 2), - INPUT_VOLUME("Line Capture Volume", 3), - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Capture Source", - .info = input_sel_info, - .get = input_sel_get, - .put = input_sel_put, - }, - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "ADC High-pass Filter Capture Enum", - .info = hpf_info, - .get = hpf_get, - .put = hpf_put, - }, -}; - -static int dg_control_filter(struct snd_kcontrol_new *template) +/* Disable built-in volume control, we have only headphone volume */ +static int xonar_control_filter(struct snd_kcontrol_new *template) { if (!strncmp(template->name, "Master Playback ", 16)) return 1; return 0; }
-static int dg_mixer_init(struct oxygen *chip) -{ - unsigned int i; - int err; - - for (i = 0; i < ARRAY_SIZE(dg_controls); ++i) { - err = snd_ctl_add(chip->card, - snd_ctl_new1(&dg_controls[i], chip)); - if (err < 0) - return err; - } - return 0; -} - -static void dump_cs4245_registers(struct oxygen *chip, - struct snd_info_buffer *buffer) -{ - struct dg *data = chip->model_data; - unsigned int i; - - snd_iprintf(buffer, "\nCS4245:"); - for (i = 1; i <= 0x10; ++i) - snd_iprintf(buffer, " %02x", data->cs4245_regs[i]); - snd_iprintf(buffer, "\n"); -} - struct oxygen_model model_xonar_dg = { .longname = "C-Media Oxygen HD Audio", .chip = "CMI8786", .init = dg_init, - .control_filter = dg_control_filter, - .mixer_init = dg_mixer_init, + .control_filter = xonar_control_filter, + .mixer_init = xonar_dg_mixer_init, .cleanup = dg_cleanup, .suspend = dg_suspend, .resume = dg_resume, @@ -596,11 +339,11 @@ struct oxygen_model model_xonar_dg = { .model_data_size = sizeof(struct dg), .device_config = PLAYBACK_0_TO_I2S | PLAYBACK_1_TO_SPDIF | - CAPTURE_0_FROM_I2S_2 | + CAPTURE_0_FROM_I2S_1 | CAPTURE_1_FROM_SPDIF, .dac_channels_pcm = 6, .dac_channels_mixer = 0, - .function_flags = OXYGEN_FUNCTION_SPI, + .function_flags = OXYGEN_FUNCTION_SPI | OXYGEN_FUNCTION_ENABLE_SPI_4_5, .dac_mclks = OXYGEN_MCLKS(256, 128, 128), .adc_mclks = OXYGEN_MCLKS(256, 128, 128), .dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST, diff -uprN linux-3.12/sound/pci/oxygen/xonar_dg.h linux-3.12-my/sound/pci/oxygen/xonar_dg.h --- linux-3.12/sound/pci/oxygen/xonar_dg.h 2013-11-04 03:41:51.000000000 +0400 +++ linux-3.12-my/sound/pci/oxygen/xonar_dg.h 2013-11-19 01:06:12.000000000 +0400 @@ -3,6 +3,48 @@
#include "oxygen.h"
+#define GPIO_MAGIC 0x0008 +#define GPIO_HP_DETECT 0x0010 +#define GPIO_INPUT_ROUTE 0x0060 +#define GPIO_HP_REAR 0x0080 +#define GPIO_OUTPUT_ENABLE 0x0100 + +#define CAPTURE_SRC_MIC 0 +#define CAPTURE_SRC_FP_MIC 1 +#define CAPTURE_SRC_LINE 2 +#define CAPTURE_SRC_AUX 3 + +#define PLAYBACK_DST_HP 0 +#define PLAYBACK_DST_HP_FP 1 +#define PLAYBACK_DST_20_CH 2 +#define PLAYBACK_DST_40_CH 3 +#define PLAYBACK_DST_51_CH 4 + extern struct oxygen_model model_xonar_dg;
+/* Xonar DG Specific Data */ +struct dg { + unsigned char cs4245_shadow[16]; + /* Output select: headphone/speakers */ + unsigned char pcm_output; + /* Input select: MIC, Line, Aux */ + unsigned char pga_source; + /* Independend capture volumes */ + char mic_vol_l; + char mic_vol_r; + bool mic_m; + char line_vol_l; + char line_vol_r; + bool line_m; + char aux_vol_l; + char aux_vol_r; + bool aux_m; +}; + +/* Xonar DG SPI/I2C control routines */ +int cs4245_write_spi(struct oxygen *chip, u8 reg); + +/* Xonar DG Mixer */ +int xonar_dg_mixer_init(struct oxygen *chip); + #endif diff -uprN linux-3.12/sound/pci/oxygen/xonar_dg_mixer.c linux-3.12-my/sound/pci/oxygen/xonar_dg_mixer.c --- linux-3.12/sound/pci/oxygen/xonar_dg_mixer.c 1970-01-01 03:00:00.000000000 +0300 +++ linux-3.12-my/sound/pci/oxygen/xonar_dg_mixer.c 2013-11-19 01:04:16.000000000 +0400 @@ -0,0 +1,536 @@ +/* + * Mixer controls for the Xonar DG/DGX + * + * 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/. + */ + +/* + * It is not good that we do not have software volume control + * as the Windows driver does. CS4361 cannot attenuate the volume. + * When we are switching from headphones to the speakers, + * which are controlled by the simple DAC CS4361, our headphones + * in the rear jack can stun us. + */ + +#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" + +/* CS4245 Headphone Channels A&B Volume Control */ +static int xonar_dg_dac_stereo_volume_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 2; + info->value.integer.min = 0; + info->value.integer.max = 255; + return 0; +} + +static int xonar_dg_dac_stereo_volume_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + unsigned int tmp; + + mutex_lock(&chip->mutex); + tmp = (~data->cs4245_shadow[CS4245_DAC_A_CTRL]) & 255; + val->value.integer.value[0] = tmp; + tmp = (~data->cs4245_shadow[CS4245_DAC_B_CTRL]) & 255; + val->value.integer.value[1] = tmp; + mutex_unlock(&chip->mutex); + return 0; +} + +static int xonar_dg_dac_stereo_volume_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + + if ((val->value.integer.value[0] > 255) || + (val->value.integer.value[0] < 0) || + (val->value.integer.value[1] > 255) || + (val->value.integer.value[1] < 0)) { + return -EINVAL; + } + mutex_lock(&chip->mutex); + data->cs4245_shadow[CS4245_DAC_A_CTRL] = ~val->value.integer.value[0]; + data->cs4245_shadow[CS4245_DAC_B_CTRL] = ~val->value.integer.value[1]; + cs4245_write_spi(chip, CS4245_DAC_A_CTRL); + cs4245_write_spi(chip, CS4245_DAC_B_CTRL); + mutex_unlock(&chip->mutex); + return 0; +} + +/* CS4245 DAC Mute */ +static int xonar_dg_dac_mute_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + + mutex_lock(&chip->mutex); + val->value.integer.value[0] = ((data->cs4245_shadow[CS4245_DAC_CTRL_1] + & CS4245_MUTE_DAC) == 0); + mutex_unlock(&chip->mutex); + return 0; +} + +static int xonar_dg_dac_mute_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + + if (val->value.integer.value[0] > 1) + return -EINVAL; + mutex_lock(&chip->mutex); + data->cs4245_shadow[CS4245_DAC_CTRL_1] &= ~CS4245_MUTE_DAC; + data->cs4245_shadow[CS4245_DAC_CTRL_1] |= + (~val->value.integer.value[0] << 2) & CS4245_MUTE_DAC; + cs4245_write_spi(chip, CS4245_DAC_CTRL_1); + mutex_unlock(&chip->mutex); + return 0; +} + +/* Xonar DG Output Select */ +static int xonar_dg_pcm_route_apply(struct oxygen *chip) +{ + struct dg *data = chip->model_data; + + if (data->pcm_output == PLAYBACK_DST_HP) { + /* Mute FP (aux output) amplifier, switch rear jack to CS4245 */ + oxygen_write8_masked(chip, OXYGEN_GPIO_DATA, 0xff, + GPIO_HP_REAR); + data->cs4245_shadow[CS4245_SIGNAL_SEL] &= + ~CS4245_A_OUT_SEL_MASK; + } else if (data->pcm_output == PLAYBACK_DST_HP_FP) { + /* + * Unmute FP amplifier, switch rear jack to CS4361, + * I2S ports 2,3,4 should be inactive + */ + oxygen_write8_masked(chip, OXYGEN_GPIO_DATA, 0x00, 0x80); + data->cs4245_shadow[CS4245_SIGNAL_SEL] &= + ~CS4245_A_OUT_SEL_MASK; + data->cs4245_shadow[CS4245_SIGNAL_SEL] |= CS4245_A_OUT_SEL_DAC; + } else { + /* + * 2.0, 4.0, 5.1: switch to CS4361, mute FP amp., + * and just change playback routing / DMA channels + */ + oxygen_write8_masked(chip, OXYGEN_GPIO_DATA, 0x00, 0x80); + data->cs4245_shadow[CS4245_SIGNAL_SEL] &= + ~CS4245_A_OUT_SEL_MASK; + } + cs4245_write_spi(chip, CS4245_SIGNAL_SEL); + return 0; +} + +static int xonar_dg_pcm_route_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const names[5] = { + "Stereo Headphones", + "Stereo Headphones FP", + "Stereo Speakers", + "4 Speakers", + "5.1 Speakers", + }; + + return snd_ctl_enum_info(info, 1, 5, names); +} + +static int xonar_dg_pcm_route_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + + mutex_lock(&chip->mutex); + val->value.integer.value[0] = data->pcm_output; + mutex_unlock(&chip->mutex); + + return 0; +} + +static int xonar_dg_pcm_route_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + int ret; + + mutex_lock(&chip->mutex); + data->pcm_output = val->value.integer.value[0]; + ret = xonar_dg_pcm_route_apply(chip); + oxygen_update_dac_routing(chip); + mutex_unlock(&chip->mutex); + + return ret; +} + +/* CS4245 PGA Source */ +static int xonar_dg_pga_mute_reg_toggle(struct oxygen *chip, bool mute); +static int xonar_dg_pga_volume_reg_write(struct oxygen *chip, char left, + char right); + +static int xonar_dg_pga_source_apply(struct oxygen *chip) +{ + struct dg *data = chip->model_data; + + data->cs4245_shadow[CS4245_ANALOG_IN] &= ~CS4245_SEL_MASK; + if (data->pga_source == CAPTURE_SRC_FP_MIC) + data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_2; + else if (data->pga_source == CAPTURE_SRC_LINE) + data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_4; + else if (data->pga_source != CAPTURE_SRC_MIC) + data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_1; + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, 0xffff, GPIO_INPUT_ROUTE); + return cs4245_write_spi(chip, CS4245_ANALOG_IN); +} + +static int xonar_dg_pga_source_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const ps_names[4] = { + "Microphone", + "Microphone FP", + "Line In", + "Aux" + }; + + return snd_ctl_enum_info(info, 1, 4, ps_names); +} + +static int xonar_dg_pga_source_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + + mutex_lock(&chip->mutex); + val->value.integer.value[0] = data->pga_source; + mutex_unlock(&chip->mutex); + + return 0; +} + +static int xonar_dg_pga_source_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + bool mute = false; + long route = val->value.integer.value[0]; + char left = 0; + char right = 0; + int ret; + + if (route > CAPTURE_SRC_AUX) + return -EINVAL; + + mutex_lock(&chip->mutex); + data->pga_source = route; + ret = xonar_dg_pga_source_apply(chip); + if (ret < 0) + goto err; + switch (route) { + case CAPTURE_SRC_MIC: + case CAPTURE_SRC_FP_MIC: + mute = data->mic_m; + left = data->mic_vol_l; + right = data->mic_vol_r; + break; + case CAPTURE_SRC_LINE: + mute = data->line_m; + left = data->line_vol_l; + right = data->line_vol_r; + break; + case CAPTURE_SRC_AUX: + mute = data->aux_m; + left = data->aux_vol_l; + right = data->aux_vol_r; + break; + } + ret = xonar_dg_pga_mute_reg_toggle(chip, mute); + if (ret < 0) + goto err; + ret = xonar_dg_pga_volume_reg_write(chip, left, right); + if (ret < 0) + goto err; +err: + mutex_unlock(&chip->mutex); + + return ret; +} + +/* Common routines to access capture volume register - Mic, Line, Aux */ +static int xonar_dg_pga_volume_reg_write(struct oxygen *chip, char left, + char right) +{ + struct dg *data = chip->model_data; + int ret; + + data->cs4245_shadow[CS4245_PGA_A_CTRL] = left; + data->cs4245_shadow[CS4245_PGA_B_CTRL] = right; + ret = cs4245_write_spi(chip, CS4245_PGA_A_CTRL); + if (ret < 0) + return ret; + return cs4245_write_spi(chip, CS4245_PGA_B_CTRL); +} + +static int xonar_dg_pga_volume_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 2; + info->value.integer.min = -24; + info->value.integer.max = 24; + return 0; +} + +static int xonar_dg_pga_volume_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + + mutex_lock(&chip->mutex); + switch (ctl->private_value) { + case CAPTURE_SRC_MIC: + val->value.integer.value[0] = data->mic_vol_l; + val->value.integer.value[1] = data->mic_vol_r; + break; + case CAPTURE_SRC_LINE: + val->value.integer.value[0] = data->line_vol_l; + val->value.integer.value[1] = data->line_vol_r; + break; + case CAPTURE_SRC_AUX: + val->value.integer.value[0] = data->aux_vol_l; + val->value.integer.value[1] = data->aux_vol_r; + break; + } + mutex_unlock(&chip->mutex); + + return 0; +} + +static int xonar_dg_pga_volume_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + long left = val->value.integer.value[0]; + long right = val->value.integer.value[1]; + int ret = 0; + + if ((left > 24) || (left < -24) || (right > 24) || (right < -24)) + return -EINVAL; + + mutex_lock(&chip->mutex); + switch (ctl->private_value) { + case CAPTURE_SRC_MIC: + data->mic_vol_l = left; + data->mic_vol_r = right; + break; + case CAPTURE_SRC_LINE: + data->line_vol_l = left; + data->line_vol_r = right; + break; + case CAPTURE_SRC_AUX: + data->aux_vol_l = left; + data->aux_vol_r = right; + break; + } + switch (data->pga_source) { + case CAPTURE_SRC_MIC: + case CAPTURE_SRC_FP_MIC: + ret = xonar_dg_pga_volume_reg_write(chip, data->mic_vol_l, + data->mic_vol_r); + break; + case CAPTURE_SRC_LINE: + ret = xonar_dg_pga_volume_reg_write(chip, data->line_vol_l, + data->line_vol_r); + break; + case CAPTURE_SRC_AUX: + ret = xonar_dg_pga_volume_reg_write(chip, data->aux_vol_l, + data->aux_vol_r); + break; + } + mutex_unlock(&chip->mutex); + + return ret; +} + +/* Common routines to access capture mute register - Mic, Line, Aux */ +static int xonar_dg_pga_mute_reg_toggle(struct oxygen *chip, bool mute) +{ + struct dg *data = chip->model_data; + + data->cs4245_shadow[CS4245_ADC_CTRL] &= ~CS4245_MUTE_ADC; + data->cs4245_shadow[CS4245_ADC_CTRL] |= ((mute == 0) << 2) & + CS4245_MUTE_ADC; + return cs4245_write_spi(chip, CS4245_ADC_CTRL); +} + +static int xonar_dg_pga_mute_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + + mutex_lock(&chip->mutex); + switch (ctl->private_value) { + case CAPTURE_SRC_MIC: + val->value.integer.value[0] = data->mic_m != false ? 1 : 0; + break; + case CAPTURE_SRC_LINE: + val->value.integer.value[0] = data->mic_m != false ? 1 : 0; + break; + case CAPTURE_SRC_AUX: + val->value.integer.value[0] = data->mic_m != false ? 1 : 0; + break; + } + mutex_unlock(&chip->mutex); + + return 0; +} + +static int xonar_dg_pga_mute_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *val) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + long tmp = val->value.integer.value[0]; + int ret = 0; + + if ((tmp > 1) || (tmp < 0)) + return -EINVAL; + + mutex_lock(&chip->mutex); + switch (ctl->private_value) { + case CAPTURE_SRC_MIC: + data->mic_m = tmp != 0; + break; + case CAPTURE_SRC_LINE: + data->line_m = tmp != 0; + break; + case CAPTURE_SRC_AUX: + data->aux_m = tmp != 0; + break; + } + switch (data->pga_source) { + case CAPTURE_SRC_MIC: + case CAPTURE_SRC_FP_MIC: + ret = xonar_dg_pga_mute_reg_toggle(chip, data->mic_m); + break; + case CAPTURE_SRC_LINE: + ret = xonar_dg_pga_mute_reg_toggle(chip, data->line_m); + break; + case CAPTURE_SRC_AUX: + ret = xonar_dg_pga_mute_reg_toggle(chip, data->aux_m); + break; + } + mutex_unlock(&chip->mutex); + + return ret; +} + +#define CAPTURE_VOLUME(xname, private) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .info = xonar_dg_pga_volume_info, \ + .get = xonar_dg_pga_volume_get, \ + .put = xonar_dg_pga_volume_put, \ + .tlv = { .p = xonar_dg_pga_db_scale, }, \ + .private_value = private, \ + } +#define CAPTURE_SWITCH(xname, private) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = snd_ctl_boolean_mono_info, \ + .get = xonar_dg_pga_mute_get, \ + .put = xonar_dg_pga_mute_put, \ + .private_value = private, \ + } + +static const DECLARE_TLV_DB_MINMAX(xonar_dg_hp_db_scale, -12550, 0); +static const DECLARE_TLV_DB_MINMAX(xonar_dg_pga_db_scale, -1200, 1200); +static const struct snd_kcontrol_new xonar_dg_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = xonar_dg_dac_stereo_volume_info, + .get = xonar_dg_dac_stereo_volume_get, + .put = xonar_dg_dac_stereo_volume_put, + .tlv = { .p = xonar_dg_hp_db_scale, }, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Switch", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_ctl_boolean_mono_info, + .get = xonar_dg_dac_mute_get, + .put = xonar_dg_dac_mute_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Output Playback Enum", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = xonar_dg_pcm_route_info, + .get = xonar_dg_pcm_route_get, + .put = xonar_dg_pcm_route_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Capture Enum", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = xonar_dg_pga_source_info, + .get = xonar_dg_pga_source_get, + .put = xonar_dg_pga_source_put, + }, + CAPTURE_VOLUME("Mic Capture Volume", CAPTURE_SRC_MIC), + CAPTURE_SWITCH("Mic Capture Switch", CAPTURE_SRC_MIC), + CAPTURE_VOLUME("Line Capture Volume", CAPTURE_SRC_LINE), + CAPTURE_SWITCH("Line Capture Switch", CAPTURE_SRC_LINE), + CAPTURE_VOLUME("Aux Capture Volume", CAPTURE_SRC_AUX), + CAPTURE_SWITCH("Aux Capture Switch", CAPTURE_SRC_AUX), +}; + +int xonar_dg_mixer_init(struct oxygen *chip) +{ + unsigned int i; + int ret; + for (i = 0; i < ARRAY_SIZE(xonar_dg_controls); i++) { + ret = snd_ctl_add(chip->card, + snd_ctl_new1(&xonar_dg_controls[i], chip)); + if (ret < 0) + return ret; + } + return 0; +}
Roman Volkov wrote:
Fixes for the Xonar DG model of the Oxygen driver:
Each of the following sentences describes a separate logical change.
+++ linux-3.12-my/sound/pci/oxygen/xonar_dg.c 2013-11-19 01:01:07.000000000 +0400
- DGX card: not tested.
- Suspend/resume: not tested
This does not belong in the source code; if some tests succeeds, we do not want to update the driver.
- SPDIF: not tested
SPDIF is handled completely by the CMI8788 itself.
- 128kHz doesn't work for CS4361 (multichannel playback)
Why do you expect 128 kHz to work?
- CS4245 Auxiliary Output is connected to the front panel headphones jack
- through amplifier.
The comment below already says this.
- GPIO 3 <- ?
- GPIO 3 <- unused (?)
Why this change?
- GPIO 5 -> route input jack to line-in (0) or mic-in (1)
- GPIO 6 -> route input jack to line-in (0) or mic-in (1)
- GPIO 5 -> ADC analog circuit power supply control for left channel (?)
- GPIO 6 -> ADC analog circuit power supply control for right channel (?)
The Xonar cards often use mechanical relays for these routing things; you should be able to hear them when switching.
- GPIO 7 -> enable rear headphone amp
- GPIO 7 -> switches green rear output jack between CS4245 / CS4361 first
- channel
0 or 1?
- GPIO 8 -> enable output to speakers
- GPIO 8 -> DAC analog circuit power supply control (?)
This relay is intended to prevent popping when powering on.
+static int cs4245_dump_regs(struct oxygen *chip, bool bRead)
Why "dump" for writing? This should be two functions.
- for (address = 0; address <= 0x10; address++) {
return bRead ? cs4245_read_spi(chip, address) :
cs4245_write_spi(chip, address);
- }
Why return from the loop?
+static void dg_init(struct oxygen *chip)
* Recommended sequence from the datasheet: reset the codec and then
* initialize it.
- oxygen_write8_masked(chip, OXYGEN_FUNCTION, 0,
OXYGEN_FUNCTION_RESET_CODEC);
This is what the _set/clear_bits functions are for.
Is the reset pin actually connected? Does the Windows driver do this?
- mdelay(1);
Use mdelay() only when sleeping is not possible; use msleep() instead.
- msleep_interruptible(1000);
When you use _interruptible, you should also check for the interrupt.
static void set_cs4245_dac_params(struct oxygen *chip, struct snd_pcm_hw_params *params)
- unsigned char tmp;
- unsigned char tmp2;
These variable names are not very descriptive.
- case PLAYBACK_DST_20_CH:
- case PLAYBACK_DST_40_CH:
- case PLAYBACK_DST_51_CH:
Why is it necessary to differentiate between those? There should be no mixer control; the driver can automatically detect what format is used. And this should not make any difference because the unused channels will be silent anyway.
+/* Put the codec into low power mode, disable audio output, save registers */
Does the Windows driver implement this?
+static void dg_suspend(struct oxygen *chip) { struct dg *data = chip->model_data;
- data->cs4245_shadow[CS4245_POWER_CTRL] = CS4245_PDN_MIC |
CS4245_PDN_ADC | CS4245_PDN_DAC | CS4245_PDN;
- cs4245_write_spi(chip, CS4245_POWER_CTRL);
- cs4245_dump_regs(chip, true);
Why reading the registers? The cs4245_shadow values should already be valid.
- .function_flags = OXYGEN_FUNCTION_SPI,
- .function_flags = OXYGEN_FUNCTION_SPI | OXYGEN_FUNCTION_ENABLE_SPI_4_5,
Why?
+++ linux-3.12-my/sound/pci/oxygen/xonar_dg.h 2013-11-19 01:06:12.000000000 +0400
This file was intended as the interface between xonar_dg.c and the generic oxygen.c driver.
That you have to add so much stuff indicates that xonar_dg.c and xonar_dg_mixer.c are not very independent and maybe should have stayed a single file.
- /* Independend capture volumes */
- char mic_vol_l;
- char mic_vol_r;
- bool mic_m;
- char line_vol_l;
- char line_vol_r;
- bool line_m;
- char aux_vol_l;
- char aux_vol_r;
- bool aux_m;
The old driver had a separate volume for front mic.
- It is not good that we do not have software volume control
- as the Windows driver does. CS4361 cannot attenuate the volume.
- When we are switching from headphones to the speakers,
- which are controlled by the simple DAC CS4361, our headphones
- in the rear jack can stun us.
This is why I did not implement a volume control in the first place.
+static int xonar_dg_pcm_route_apply(struct oxygen *chip)
oxygen_write8_masked(chip, OXYGEN_GPIO_DATA, 0x00, 0x80);
Use _set_bits and the GPIO_ symbol.
+static int xonar_dg_pga_volume_get(struct snd_kcontrol *ctl,
struct snd_ctl_elem_value *val)
- switch (ctl->private_value) {
- case CAPTURE_SRC_MIC:
- case CAPTURE_SRC_LINE:
- case CAPTURE_SRC_AUX:
And FP_MIC? (Also elsewhere.)
Regards, Clemens
В Tue, 19 Nov 2013 23:23:54 +0100 Clemens Ladisch clemens@ladisch.de пишет:
Roman Volkov wrote:
Fixes for the Xonar DG model of the Oxygen driver:
Each of the following sentences describes a separate logical change.
Yea, have some problems with naming :). Will try.
+++ linux-3.12-my/sound/pci/oxygen/xonar_dg.c 2013-11-19 01:01:07.000000000 +0400
- DGX card: not tested.
- Suspend/resume: not tested
This does not belong in the source code; if some tests succeeds, we do not want to update the driver.
Will move that to the patch notes.
- SPDIF: not tested
SPDIF is handled completely by the CMI8788 itself.
- 128kHz doesn't work for CS4361 (multichannel playback)
Why do you expect 128 kHz to work?
128kHz will not work because CS4361 can't detect clocks for the following mode: LRCK = 128khz MCLK = 128x I tried to use MCLKs sequence 512,256,128, but unfortunately, then 32kHz does not work for CS4361. Is the frequency 128kHz valid for ALSA?
- CS4245 Auxiliary Output is connected to the front panel
headphones jack
- through amplifier.
The comment below already says this.
- GPIO 3 <- ?
- GPIO 3 <- unused (?)
Why this change?
Will remove overcommenting and reduce changes.
- GPIO 5 -> route input jack to line-in (0) or mic-in (1)
- GPIO 6 -> route input jack to line-in (0) or mic-in (1)
- GPIO 5 -> ADC analog circuit power supply control for left
channel (?)
- GPIO 6 -> ADC analog circuit power supply control for right
channel (?)
The Xonar cards often use mechanical relays for these routing things; you should be able to hear them when switching.
Yes, GPIO 7 swiches that relay.
- GPIO 7 -> enable rear headphone amp
- GPIO 7 -> switches green rear output jack between CS4245 /
CS4361 first
- channel
0 or 1?
- GPIO 8 -> enable output to speakers
- GPIO 8 -> DAC analog circuit power supply control (?)
This relay is intended to prevent popping when powering on.
GPIO 8 "powers up" or I don't know what it exactly does, but it doesn't switch the relay. It "enables" the audio output, headphone amp becomes connected to the DACs output, probably the same for multichannel outputs. During initialization of the windows driver, this GPIO becomes active at the last step. And perhaps my anti-pop delay of 1 second is too long. Maybe, I'll find better way to prevent pops.
+static int cs4245_dump_regs(struct oxygen *chip, bool bRead)
Why "dump" for writing? This should be two functions.
- for (address = 0; address <= 0x10; address++) {
return bRead ? cs4245_read_spi(chip, address) :
cs4245_write_spi(chip, address);
- }
Why return from the loop?
Oh, my mistake. Too quickly performed optimizations :). This only affects suspend\resume logic, so I missed out this after testing.
+static void dg_init(struct oxygen *chip)
* Recommended sequence from the datasheet: reset the
codec and then
* initialize it.
- oxygen_write8_masked(chip, OXYGEN_FUNCTION, 0,
OXYGEN_FUNCTION_RESET_CODEC);
This is what the _set/clear_bits functions are for.
Thanks for the hint.
Is the reset pin actually connected? Does the Windows driver do this?
Should be, otherwise this is bad design. Windows driver probably not does that, but honestly, this driver is slightly buggy: sometimes (rarely) I get completely distorted sound when I power up the Windows machine. In the Linux driver I do not have this issue.
- mdelay(1);
Use mdelay() only when sleeping is not possible; use msleep() instead.
msleep() is not recommended for small delays (1ms-20ms). Using mdelay() here is okay. Don't use mdelay for long delays (more than 20ms).
- msleep_interruptible(1000);
When you use _interruptible, you should also check for the interrupt.
Will use just msleep().
static void set_cs4245_dac_params(struct oxygen *chip, struct snd_pcm_hw_params *params)
- unsigned char tmp;
- unsigned char tmp2;
These variable names are not very descriptive.
Ok
- case PLAYBACK_DST_20_CH:
- case PLAYBACK_DST_40_CH:
- case PLAYBACK_DST_51_CH:
Why is it necessary to differentiate between those? There should be no mixer control; the driver can automatically detect what format is used. And this should not make any difference because the unused channels will be silent anyway.
Just as in the windows driver. Should I change this to "HP\HP FP\Speakers"?
+/* Put the codec into low power mode, disable audio output, save registers */
Does the Windows driver implement this?
I don't know. Linux docs says I should put my hardware into low power mode and I do that. However I can't test this on my machine - my display remains black after resume (VGA driver) and computer freezes at all when I use NVIDIA proprietary driver... Maybe will test this on another machine later.
+static void dg_suspend(struct oxygen *chip) { struct dg *data = chip->model_data;
- data->cs4245_shadow[CS4245_POWER_CTRL] = CS4245_PDN_MIC |
CS4245_PDN_ADC | CS4245_PDN_DAC | CS4245_PDN;
- cs4245_write_spi(chip, CS4245_POWER_CTRL);
- cs4245_dump_regs(chip, true);
Why reading the registers? The cs4245_shadow values should already be valid.
Just to validate again :)
- .function_flags = OXYGEN_FUNCTION_SPI,
- .function_flags = OXYGEN_FUNCTION_SPI |
OXYGEN_FUNCTION_ENABLE_SPI_4_5,
Why?
Because this just works and as in the Windows driver. IFAIK, this sets bit 7 at register 50h in the CMI chip (1: select SPI chip 4, 5 enable function).
+++ linux-3.12-my/sound/pci/oxygen/xonar_dg.h 2013-11-19 01:06:12.000000000 +0400
This file was intended as the interface between xonar_dg.c and the generic oxygen.c driver.
That you have to add so much stuff indicates that xonar_dg.c and xonar_dg_mixer.c are not very independent and maybe should have stayed a single file.
But moving mixer to another file so simplifies things... Currently, xonar_dg.c is at upper level of hierarchy, probably I should change that? (The mixer controls the driver, not otherwise)
- /* Independend capture volumes */
- char mic_vol_l;
- char mic_vol_r;
- bool mic_m;
- char line_vol_l;
- char line_vol_r;
- bool line_m;
- char aux_vol_l;
- char aux_vol_r;
- bool aux_m;
The old driver had a separate volume for front mic.
Will add this one.
- It is not good that we do not have software volume control
- as the Windows driver does. CS4361 cannot attenuate the volume.
- When we are switching from headphones to the speakers,
- which are controlled by the simple DAC CS4361, our headphones
- in the rear jack can stun us.
This is why I did not implement a volume control in the first place.
Honestly, this is a common problem with ALSA, headphones without volume regulator, and flat_volumes = yes. It's better to have this regulator I think...
+static int xonar_dg_pcm_route_apply(struct oxygen *chip)
oxygen_write8_masked(chip, OXYGEN_GPIO_DATA, 0x00,
0x80);
Use _set_bits and the GPIO_ symbol.
Ok
+static int xonar_dg_pga_volume_get(struct snd_kcontrol *ctl,
struct snd_ctl_elem_value *val)
- switch (ctl->private_value) {
- case CAPTURE_SRC_MIC:
- case CAPTURE_SRC_LINE:
- case CAPTURE_SRC_AUX:
And FP_MIC? (Also elsewhere.)
CAPTURE_SRC_MIC private_value is common for both FP and rear, see snd_kcontrol_new array.
Regards, Clemens
participants (2)
-
Clemens Ladisch
-
Roman Volkov