Created
December 6, 2013 20:15
-
-
Save ipha/7831385 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff -uprN linux-3.12/sound/pci/oxygen/oxygen.h linux-3.12-my/sound/pci/oxygen/oxygen.h | |
--- linux-3.12/sound/pci/oxygen/oxygen.h 2013-11-04 03:41:51.000000000 +0400 | |
+++ linux-3.12-my/sound/pci/oxygen/oxygen.h 2013-11-20 22:20:44.000000000 +0400 | |
@@ -198,7 +198,7 @@ void oxygen_write_ac97(struct oxygen *ch | |
void oxygen_write_ac97_masked(struct oxygen *chip, unsigned int codec, | |
unsigned int index, u16 data, u16 mask); | |
-void oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data); | |
+int oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data); | |
void oxygen_write_i2c(struct oxygen *chip, u8 device, u8 map, u8 data); | |
void oxygen_reset_uart(struct oxygen *chip); | |
diff -uprN linux-3.12/sound/pci/oxygen/oxygen_io.c linux-3.12-my/sound/pci/oxygen/oxygen_io.c | |
--- linux-3.12/sound/pci/oxygen/oxygen_io.c 2013-11-04 03:41:51.000000000 +0400 | |
+++ linux-3.12-my/sound/pci/oxygen/oxygen_io.c 2013-11-20 15:34:14.000000000 +0400 | |
@@ -194,23 +194,34 @@ void oxygen_write_ac97_masked(struct oxy | |
} | |
EXPORT_SYMBOL(oxygen_write_ac97_masked); | |
-void oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data) | |
+static int oxygen_wait_spi(struct oxygen *chip) | |
{ | |
- unsigned int count; | |
+ /* Higher interval to be sure - 400 microseconds */ | |
+ unsigned int count = 100; | |
- /* should not need more than 30.72 us (24 * 1.28 us) */ | |
- count = 10; | |
- while ((oxygen_read8(chip, OXYGEN_SPI_CONTROL) & OXYGEN_SPI_BUSY) | |
- && count > 0) { | |
+ for (;;) { | |
+ if ((oxygen_read8(chip, OXYGEN_SPI_CONTROL) & | |
+ OXYGEN_SPI_BUSY) == 0) | |
+ break; | |
+ if (count == 0) { | |
+ snd_printk(KERN_ERR "oxygen: spi wait timeout\n"); | |
+ return -EIO; | |
+ } | |
udelay(4); | |
- --count; | |
+ count--; | |
} | |
+ return 0; | |
+} | |
+int oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data) | |
+{ | |
oxygen_write8(chip, OXYGEN_SPI_DATA1, data); | |
oxygen_write8(chip, OXYGEN_SPI_DATA2, data >> 8); | |
if (control & OXYGEN_SPI_DATA_LENGTH_3) | |
oxygen_write8(chip, OXYGEN_SPI_DATA3, data >> 16); | |
oxygen_write8(chip, OXYGEN_SPI_CONTROL, control); | |
+ | |
+ return oxygen_wait_spi(chip); | |
} | |
EXPORT_SYMBOL(oxygen_write_spi); | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff -uprN linux-3.12/sound/pci/oxygen/oxygen_regs.h linux-3.12-my/sound/pci/oxygen/oxygen_regs.h | |
--- linux-3.12/sound/pci/oxygen/oxygen_regs.h 2013-11-04 03:41:51.000000000 +0400 | |
+++ linux-3.12-my/sound/pci/oxygen/oxygen_regs.h 2013-11-20 12:08:07.000000000 +0400 | |
@@ -318,6 +318,7 @@ | |
#define OXYGEN_PLAY_MUTE23 0x0002 | |
#define OXYGEN_PLAY_MUTE45 0x0004 | |
#define OXYGEN_PLAY_MUTE67 0x0008 | |
+#define OXYGEN_PLAY_MUTE_MASK 0x000f | |
#define OXYGEN_PLAY_MULTICH_MASK 0x0010 | |
#define OXYGEN_PLAY_MULTICH_I2S_DAC 0x0000 | |
#define OXYGEN_PLAY_MULTICH_AC97 0x0010 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff -uprN linux-3.12/sound/pci/oxygen/oxygen_mixer.c linux-3.12-my/sound/pci/oxygen/oxygen_mixer.c | |
--- linux-3.12/sound/pci/oxygen/oxygen_mixer.c 2013-11-04 03:41:51.000000000 +0400 | |
+++ linux-3.12-my/sound/pci/oxygen/oxygen_mixer.c 2013-11-20 19:13:09.000000000 +0400 | |
@@ -190,6 +190,7 @@ void oxygen_update_dac_routing(struct ox | |
if (chip->model.update_center_lfe_mix) | |
chip->model.update_center_lfe_mix(chip, chip->dac_routing > 2); | |
} | |
+EXPORT_SYMBOL(oxygen_update_dac_routing); | |
static int upmix_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) | |
{ | |
diff -uprN linux-3.12/sound/pci/oxygen/cs4245.h linux-3.12-my/sound/pci/oxygen/cs4245.h | |
--- linux-3.12/sound/pci/oxygen/cs4245.h 2013-11-04 03:41:51.000000000 +0400 | |
+++ linux-3.12-my/sound/pci/oxygen/cs4245.h 2013-11-20 12:08:07.000000000 +0400 | |
@@ -103,5 +103,6 @@ | |
#define CS4245_ADC_UNDRFL 0x01 | |
-#define CS4245_SPI_ADDRESS (0x9e << 16) | |
-#define CS4245_SPI_WRITE (0 << 16) | |
+#define CS4245_SPI_ADDRESS 0x9e | |
+#define CS4245_SPI_WRITE 0 | |
+#define CS4245_SPI_READ 1 | |
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-21 13:41:44.000000000 +0400 | |
@@ -3,6 +3,51 @@ | |
#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_MULTICH 2 | |
+ | |
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, FP Mic, Line, Aux */ | |
+ unsigned char pga_source; | |
+ /* Independent capture volume */ | |
+ char cap_vol[4][2]; | |
+ bool cap_mute[4]; | |
+}; | |
+ | |
+/* Xonar DG control routines */ | |
+int cs4245_write_spi(struct oxygen *chip, u8 reg); | |
+int cs4245_read_spi(struct oxygen *chip, u8 reg); | |
+int cs4245_read_shadow(struct oxygen *chip); | |
+int cs4245_write_shadow(struct oxygen *chip); | |
+void dg_init(struct oxygen *chip); | |
+void set_cs4245_dac_params(struct oxygen *chip, | |
+ struct snd_pcm_hw_params *params); | |
+void set_cs4245_adc_params(struct oxygen *chip, | |
+ struct snd_pcm_hw_params *params); | |
+unsigned int adjust_dg_dac_routing(struct oxygen *chip, | |
+ unsigned int play_routing); | |
+void dump_cs4245_registers(struct oxygen *chip, | |
+ struct snd_info_buffer *buffer); | |
+void dg_suspend(struct oxygen *chip); | |
+void dg_resume(struct oxygen *chip); | |
+void dg_cleanup(struct oxygen *chip); | |
+ | |
#endif | |
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-21 16:35:02.000000000 +0400 | |
@@ -20,29 +20,37 @@ | |
* Xonar DG/DGX | |
* ------------ | |
* | |
+ * CS4245/CS4361 both will mute all outputs if any clock ratio of | |
+ * any I²S channel 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 -> 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 (mechanical relay) | |
+ * 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,553 +64,214 @@ | |
#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) | |
-{ | |
- 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; | |
-} | |
- | |
-static void cs4245_write_cached(struct oxygen *chip, unsigned int reg, u8 value) | |
-{ | |
- struct dg *data = chip->model_data; | |
- | |
- 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; | |
- | |
- 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); | |
-} | |
- | |
-static void cs4245_init(struct oxygen *chip) | |
+int cs4245_write_spi(struct oxygen *chip, u8 reg) | |
{ | |
struct dg *data = chip->model_data; | |
- 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"); | |
-} | |
- | |
-static void dg_output_enable(struct oxygen *chip) | |
-{ | |
- msleep(2500); | |
- oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE); | |
+ 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 dg_init(struct oxygen *chip) | |
+int cs4245_read_spi(struct oxygen *chip, u8 reg) | |
{ | |
struct dg *data = chip->model_data; | |
+ int ret; | |
- data->output_sel = 0; | |
- data->input_sel = 3; | |
- data->hp_vol_att = 2 * 16; | |
+ 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_init(chip); | |
- | |
- 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); | |
-} | |
- | |
-static void dg_cleanup(struct oxygen *chip) | |
-{ | |
- oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE); | |
-} | |
- | |
-static void dg_suspend(struct oxygen *chip) | |
-{ | |
- dg_cleanup(chip); | |
-} | |
- | |
-static void dg_resume(struct oxygen *chip) | |
-{ | |
- cs4245_registers_init(chip); | |
- dg_output_enable(chip); | |
-} | |
- | |
-static void set_cs4245_dac_params(struct oxygen *chip, | |
- struct snd_pcm_hw_params *params) | |
-{ | |
- struct dg *data = chip->model_data; | |
- u8 value; | |
- | |
- 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); | |
-} | |
- | |
-static void set_cs4245_adc_params(struct oxygen *chip, | |
- struct snd_pcm_hw_params *params) | |
-{ | |
- struct dg *data = chip->model_data; | |
- u8 value; | |
- | |
- 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; | |
-} | |
- | |
-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); | |
-} | |
+ 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; | |
-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; | |
+ data->cs4245_shadow[reg] = oxygen_read8(chip, OXYGEN_SPI_DATA1); | |
- 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) | |
+int cs4245_read_shadow(struct oxygen *chip) | |
{ | |
- 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; | |
+ unsigned char address; | |
+ int ret; | |
- 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); | |
+ for (address = 0; address <= 0x10; address++) { | |
+ ret = cs4245_read_spi(chip, address); | |
+ if (ret < 0) | |
+ return ret; | |
} | |
- 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) | |
+int cs4245_write_shadow(struct oxygen *chip) | |
{ | |
- 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 char address; | |
+ int ret; | |
- 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); | |
- } | |
+ for (address = 0; address <= 0x10; address++) { | |
+ ret = cs4245_write_spi(chip, address); | |
+ if (ret < 0) | |
+ return ret; | |
} | |
- 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; | |
} | |
-static int input_vol_put(struct snd_kcontrol *ctl, | |
- struct snd_ctl_elem_value *value) | |
+static void cs4245_init(struct oxygen *chip) | |
{ | |
- 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; | |
-} | |
+ /* save the initial state */ | |
+ cs4245_read_shadow(chip); | |
-static DECLARE_TLV_DB_SCALE(cs4245_pga_db_scale, -1200, 50, 0); | |
+ /* | |
+ * 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; | |
-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" | |
- }; | |
+ cs4245_write_shadow(chip); | |
- return snd_ctl_enum_info(info, 1, 4, names); | |
+ snd_component_add(chip->card, "CS4245"); | |
} | |
-static int input_sel_get(struct snd_kcontrol *ctl, | |
- struct snd_ctl_elem_value *value) | |
+void dg_init(struct oxygen *chip) | |
{ | |
- struct oxygen *chip = ctl->private_data; | |
- struct dg *data = chip->model_data; | |
+ /* | |
+ * The pin XGPIO1 as XGPIO1, gpios 8,7,6,5,1,0 as outputs. | |
+ */ | |
- mutex_lock(&chip->mutex); | |
- value->value.enumerated.item[0] = data->input_sel; | |
- mutex_unlock(&chip->mutex); | |
- return 0; | |
+ 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(2500); | |
+ oxygen_write16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE | | |
+ GPIO_HP_REAR | GPIO_INPUT_ROUTE); | |
} | |
-static int input_sel_put(struct snd_kcontrol *ctl, | |
- struct snd_ctl_elem_value *value) | |
+void set_cs4245_dac_params(struct oxygen *chip, | |
+ struct snd_pcm_hw_params *params) | |
{ | |
- 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; | |
+ unsigned char dac_ctrl; | |
+ unsigned char mclk_freq; | |
- 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); | |
+ 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; | |
} | |
- mutex_unlock(&chip->mutex); | |
- return changed; | |
+ 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); | |
} | |
-static int hpf_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info) | |
+void set_cs4245_adc_params(struct oxygen *chip, | |
+ struct snd_pcm_hw_params *params) | |
{ | |
- static const char *const names[2] = { "Active", "Frozen" }; | |
+ struct dg *data = chip->model_data; | |
+ unsigned char adc_ctrl; | |
+ unsigned char mclk_freq; | |
- return snd_ctl_enum_info(info, 1, 2, names); | |
+ 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 int hpf_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) | |
+unsigned int adjust_dg_dac_routing(struct oxygen *chip, | |
+ unsigned int play_routing) | |
{ | |
- struct oxygen *chip = ctl->private_data; | |
struct dg *data = chip->model_data; | |
+ unsigned int routing = 0; | |
- value->value.enumerated.item[0] = | |
- !!(data->cs4245_regs[CS4245_ADC_CTRL] & CS4245_HPF_FREEZE); | |
- return 0; | |
+ 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_MULTICH: | |
+ 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; | |
+ } | |
+ return routing; | |
} | |
-static int hpf_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) | |
+void dump_cs4245_registers(struct oxygen *chip, | |
+ struct snd_info_buffer *buffer) | |
{ | |
- struct oxygen *chip = ctl->private_data; | |
struct dg *data = chip->model_data; | |
- u8 reg; | |
- int changed; | |
+ unsigned int reg; | |
- 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, | |
- }, | |
-}; | |
+ 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 dg_control_filter(struct snd_kcontrol_new *template) | |
+void dg_suspend(struct oxygen *chip) | |
{ | |
- if (!strncmp(template->name, "Master Playback ", 16)) | |
- return 1; | |
- return 0; | |
+ oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE); | |
} | |
-static int dg_mixer_init(struct oxygen *chip) | |
+void dg_resume(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; | |
+ cs4245_write_shadow(chip); | |
+ msleep(2500); | |
+ oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE); | |
} | |
-static void dump_cs4245_registers(struct oxygen *chip, | |
- struct snd_info_buffer *buffer) | |
+void dg_cleanup(struct oxygen *chip) | |
{ | |
- 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"); | |
+ dg_suspend(chip); | |
} | |
- | |
-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, | |
- .cleanup = dg_cleanup, | |
- .suspend = dg_suspend, | |
- .resume = dg_resume, | |
- .set_dac_params = set_cs4245_dac_params, | |
- .set_adc_params = set_cs4245_adc_params, | |
- .adjust_dac_routing = adjust_dg_dac_routing, | |
- .dump_registers = dump_cs4245_registers, | |
- .model_data_size = sizeof(struct dg), | |
- .device_config = PLAYBACK_0_TO_I2S | | |
- PLAYBACK_1_TO_SPDIF | | |
- CAPTURE_0_FROM_I2S_2 | | |
- CAPTURE_1_FROM_SPDIF, | |
- .dac_channels_pcm = 6, | |
- .dac_channels_mixer = 0, | |
- .function_flags = OXYGEN_FUNCTION_SPI, | |
- .dac_mclks = OXYGEN_MCLKS(256, 128, 128), | |
- .adc_mclks = OXYGEN_MCLKS(256, 128, 128), | |
- .dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST, | |
- .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST, | |
-}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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-20 19:04:54.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_mixer.o xonar_dg.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_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-21 14:05:55.000000000 +0400 | |
@@ -0,0 +1,483 @@ | |
+/* | |
+ * Mixer controls for the Xonar DG/DGX | |
+ * | |
+ * Copyright (c) Clemens Ladisch <clemens at ladisch.de> | |
+ * Copyright (c) Roman Volkov <v1ron at 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; | |
+ | |
+ data->cs4245_shadow[CS4245_SIGNAL_SEL] &= | |
+ ~CS4245_A_OUT_SEL_MASK; | |
+ if (data->pcm_output == PLAYBACK_DST_HP) { | |
+ /* Mute FP (aux output) amplifier, switch rear jack to CS4245 */ | |
+ oxygen_set_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR); | |
+ } 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_clear_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR); | |
+ 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 change playback routing / DMA channels | |
+ */ | |
+ oxygen_clear_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR); | |
+ } | |
+ 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[3] = { | |
+ "Stereo Headphones", | |
+ "Stereo Headphones FP", | |
+ "Multichannel", | |
+ }; | |
+ | |
+ return snd_ctl_enum_info(info, 1, 3, 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_set_bits16(chip, OXYGEN_GPIO_DATA, 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; | |
+} | |
+ | |
+/* Switch the source and change volume/mute */ | |
+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; | |
+ long route = val->value.integer.value[0]; | |
+ int ret; | |
+ | |
+ if ((route > CAPTURE_SRC_AUX) || (route < 0)) | |
+ return -EINVAL; | |
+ | |
+ mutex_lock(&chip->mutex); | |
+ data->pga_source = route; | |
+ ret = xonar_dg_pga_source_apply(chip); | |
+ if (ret < 0) | |
+ goto err; | |
+ ret = xonar_dg_pga_mute_reg_toggle(chip, data->cap_mute[route]); | |
+ if (ret < 0) | |
+ goto err; | |
+ ret = xonar_dg_pga_volume_reg_write(chip, | |
+ data->cap_vol[route][0], | |
+ data->cap_vol[route][1]); | |
+ 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); | |
+ val->value.integer.value[0] = data->cap_vol[ctl->private_value][0]; | |
+ val->value.integer.value[1] = data->cap_vol[ctl->private_value][1]; | |
+ 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); | |
+ data->cap_vol[ctl->private_value][0] = left; | |
+ data->cap_vol[ctl->private_value][1] = right; | |
+ ret = xonar_dg_pga_volume_reg_write(chip, | |
+ data->cap_vol[data->pga_source][0], | |
+ data->cap_vol[data->pga_source][1]); | |
+ 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); | |
+ val->value.integer.value[0] = (long)data->cap_mute[ctl->private_value]; | |
+ 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); | |
+ data->cap_mute[ctl->private_value] = tmp != 0; | |
+ ret = xonar_dg_pga_mute_reg_toggle(chip, | |
+ data->cap_mute[data->pga_source]); | |
+ 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("FP Mic Capture Volume", CAPTURE_SRC_FP_MIC), | |
+ CAPTURE_SWITCH("FP Mic Capture Switch", CAPTURE_SRC_FP_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; | |
+} | |
+ | |
+/* 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; | |
+} | |
+ | |
+struct oxygen_model model_xonar_dg = { | |
+ .longname = "C-Media Oxygen HD Audio", | |
+ .chip = "CMI8786", | |
+ .init = dg_init, | |
+ .control_filter = xonar_control_filter, | |
+ .mixer_init = xonar_dg_mixer_init, | |
+ .cleanup = dg_cleanup, | |
+ .suspend = dg_suspend, | |
+ .resume = dg_resume, | |
+ .set_dac_params = set_cs4245_dac_params, | |
+ .set_adc_params = set_cs4245_adc_params, | |
+ .adjust_dac_routing = adjust_dg_dac_routing, | |
+ .dump_registers = dump_cs4245_registers, | |
+ .model_data_size = sizeof(struct dg), | |
+ .device_config = PLAYBACK_0_TO_I2S | | |
+ PLAYBACK_1_TO_SPDIF | | |
+ CAPTURE_0_FROM_I2S_1 | | |
+ CAPTURE_1_FROM_SPDIF, | |
+ .dac_channels_pcm = 6, | |
+ .dac_channels_mixer = 0, | |
+ .function_flags = OXYGEN_FUNCTION_SPI, | |
+ .dac_mclks = OXYGEN_MCLKS(256, 128, 128), | |
+ .adc_mclks = OXYGEN_MCLKS(256, 128, 128), | |
+ .dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST, | |
+ .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST, | |
+}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment