[alsa-devel] [PATCH v3 01/16] ALSA: Oxygen: Add the separate SPI waiting function
This function performs waiting when the SPI bus completes a transaction. Timeout error checking introduced and the timeout increased to 400 from 40.
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/oxygen_io.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+)
diff --git a/sound/pci/oxygen/oxygen_io.c b/sound/pci/oxygen/oxygen_io.c index 521eae4..a917f37 100644 --- a/sound/pci/oxygen/oxygen_io.c +++ b/sound/pci/oxygen/oxygen_io.c @@ -194,6 +194,21 @@ void oxygen_write_ac97_masked(struct oxygen *chip, unsigned int codec, } EXPORT_SYMBOL(oxygen_write_ac97_masked);
+static int oxygen_wait_spi(struct oxygen *chip) +{ + /* Higher timeout to be sure: 400 microseconds */ + unsigned int count; + for (count = 100; count > 0; count--) { + if ((oxygen_read8(chip, OXYGEN_SPI_CONTROL) & + OXYGEN_SPI_BUSY) == 0) + return 0; + udelay(4); + --count; + } + snd_printk(KERN_ERR "Oxygen: SPI wait timeout\n"); + return -EIO; +} + void oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data) { unsigned int count;
The SPI writing function modified to use the new waiting function. Return value changed from void to int, so it can return error codes. Older drivers just ignore that return value, the Xonar DG/DGX driver handles this value. We need to wait AFTER initiating the SPI transaction, otherwise read operation will not work.
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/oxygen.h | 2 +- sound/pci/oxygen/oxygen_io.c | 15 ++++----------- 2 files changed, 5 insertions(+), 12 deletions(-)
diff --git a/sound/pci/oxygen/oxygen.h b/sound/pci/oxygen/oxygen.h index 09a24b2..c10ab07 100644 --- a/sound/pci/oxygen/oxygen.h +++ b/sound/pci/oxygen/oxygen.h @@ -198,7 +198,7 @@ void oxygen_write_ac97(struct oxygen *chip, unsigned int codec, 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 --git a/sound/pci/oxygen/oxygen_io.c b/sound/pci/oxygen/oxygen_io.c index a917f37..fb9c826 100644 --- a/sound/pci/oxygen/oxygen_io.c +++ b/sound/pci/oxygen/oxygen_io.c @@ -209,23 +209,16 @@ static int oxygen_wait_spi(struct oxygen *chip) return -EIO; }
-void oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data) +int oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data) { - unsigned int count; - - /* 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) { - udelay(4); - --count; - } - + /* We need to wait AFTER initiating the SPI transaction, + otherwise read operations will not work */ 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);
The Xonar DG/DGX driver needs this mask to mute unnecessary channels.
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/oxygen_regs.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/sound/pci/oxygen/oxygen_regs.h b/sound/pci/oxygen/oxygen_regs.h index 63dc7a0..8c191ba 100644 --- a/sound/pci/oxygen/oxygen_regs.h +++ b/sound/pci/oxygen/oxygen_regs.h @@ -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
The driver needs this constant to perform SPI read operations. The shift from CS4245_SPI_ADDRESS and CS4245_SPI_WRITE constants removed, since when we need to send two bytes instead of three in the SPI packet, we need to shift left by 8 instead of 16.
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/cs4245.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/sound/pci/oxygen/cs4245.h b/sound/pci/oxygen/cs4245.h index 5e0197e..3dd487a 100644 --- a/sound/pci/oxygen/cs4245.h +++ b/sound/pci/oxygen/cs4245.h @@ -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
Roman Volkov wrote:
The driver needs this constant to perform SPI read operations. The shift from CS4245_SPI_ADDRESS and CS4245_SPI_WRITE constants removed, since when we need to send two bytes instead of three in the SPI packet, we need to shift left by 8 instead of 16.
-#define CS4245_SPI_ADDRESS (0x9e << 16) -#define CS4245_SPI_WRITE (0 << 16) +#define CS4245_SPI_ADDRESS 0x9e +#define CS4245_SPI_WRITE 0
This change alone breaks the driver.
All patches must be self-contained, which is important to be able to apply the patch series partially during debugging.
Please merge this with the patch that changes the SPI code.
Regards, Clemens
When the user switches stereo output to multichannel or vice versa, the driver needs to update the channel routing. Instead of creating additional subroutines, I better export existing oxygen_update_dac_routing symbol from the oxygen mixer and call this function. It calls model.adjust_dac_routing() and my function does the work.
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/oxygen_mixer.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/sound/pci/oxygen/oxygen_mixer.c b/sound/pci/oxygen/oxygen_mixer.c index c0dbb52..5988e04 100644 --- a/sound/pci/oxygen/oxygen_mixer.c +++ b/sound/pci/oxygen/oxygen_mixer.c @@ -190,6 +190,7 @@ void oxygen_update_dac_routing(struct oxygen *chip) 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) {
Some additional information in comments and my copyright added.
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/xonar_dg.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/sound/pci/oxygen/xonar_dg.c b/sound/pci/oxygen/xonar_dg.c index 77acd79..b806cc1 100644 --- a/sound/pci/oxygen/xonar_dg.c +++ b/sound/pci/oxygen/xonar_dg.c @@ -1,8 +1,8 @@ /* - * card driver for the Xonar DG/DGX + * 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. @@ -20,27 +20,35 @@ * Xonar DG/DGX * ------------ * + * CS4245 and CS4361 both will mute all outputs if any clock ratio + * is invalid. + * * CMI8788: * * SPI 0 -> CS4245 * + * Playback: * I²S 1 -> CS4245 * I²S 2 -> CS4361 (center/LFE) * I²S 3 -> CS4361 (surround) * I²S 4 -> CS4361 (front) + * Capture: + * I²S ADC 1 <- CS4245 * * GPIO 3 <- ? * GPIO 4 <- headphone detect - * GPIO 5 -> 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 5 -> enable ADC analog circuit for the left channel + * GPIO 6 -> enable ADC analog circuit for the right channel + * GPIO 7 -> switch green rear output jack between CS4245 and + * and the first channel of CS4361 (mechanical relay) * GPIO 8 -> enable output to speakers * * CS4245: * + * input 0 <- mic * input 1 <- aux * input 2 <- front mic - * input 4 <- line/mic + * input 4 <- line * DAC out -> headphones * aux out -> front panel headphones */
Some additional constants added to the xonar_dg.h file: capture and playback sources. GPIO_* constants and the dg struct moved to the header file from the xonar_dg.c file.
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/xonar_dg.c | 14 -------------- sound/pci/oxygen/xonar_dg.h | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 14 deletions(-)
diff --git a/sound/pci/oxygen/xonar_dg.c b/sound/pci/oxygen/xonar_dg.c index b806cc1..2a0ad6e 100644 --- a/sound/pci/oxygen/xonar_dg.c +++ b/sound/pci/oxygen/xonar_dg.c @@ -64,20 +64,6 @@ #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; diff --git a/sound/pci/oxygen/xonar_dg.h b/sound/pci/oxygen/xonar_dg.h index 5688d78..0812692 100644 --- a/sound/pci/oxygen/xonar_dg.h +++ b/sound/pci/oxygen/xonar_dg.h @@ -3,6 +3,29 @@
#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 + +struct dg { + unsigned int output_sel; + s8 input_vol[4][2]; + unsigned int input_sel; + u8 hp_vol_att; + u8 cs4245_regs[0x11]; +}; + extern struct oxygen_model model_xonar_dg;
#endif
The existing write function slightly changed. The new read function added. This read function used for creating initial registers dump and may be used for debugging purposes. SPI operations are cached, so there is a new function to work with the cache (shadow).
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/xonar_dg.c | 60 +++++++++++++++++++++++++++++++++++++++++++ sound/pci/oxygen/xonar_dg.h | 7 +++++ 2 files changed, 67 insertions(+)
diff --git a/sound/pci/oxygen/xonar_dg.c b/sound/pci/oxygen/xonar_dg.c index 2a0ad6e..0a8ebe1 100644 --- a/sound/pci/oxygen/xonar_dg.c +++ b/sound/pci/oxygen/xonar_dg.c @@ -64,6 +64,66 @@ #include "xonar_dg.h" #include "cs4245.h"
+int cs4245_write_spi(struct oxygen *chip, u8 reg) +{ + struct dg *data = chip->model_data; + register unsigned int packet; + + packet = (u32)reg; + packet <<= 8; + packet |= (CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 16; + packet |= data->cs4245_shadow[reg]; + + return oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_3 | + OXYGEN_SPI_CLOCK_1280 | + (0 << OXYGEN_SPI_CODEC_SHIFT) | + OXYGEN_SPI_CEN_LATCH_CLOCK_HI, + packet); +} + +int cs4245_read_spi(struct oxygen *chip, u8 addr) +{ + struct dg *data = chip->model_data; + int ret; + + ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_2 | + OXYGEN_SPI_CEN_LATCH_CLOCK_HI | + OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT), + ((CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 8) | addr); + if (ret < 0) + return ret; + + ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_2 | + OXYGEN_SPI_CEN_LATCH_CLOCK_HI | + OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT), + (CS4245_SPI_ADDRESS | CS4245_SPI_READ) << 8); + if (ret < 0) + return ret; + + data->cs4245_shadow[addr] = oxygen_read8(chip, OXYGEN_SPI_DATA1); + + return 0; +} + +int cs4245_shadow_control(struct oxygen *chip, enum cs4245_shadow_operation op) +{ + unsigned char addr; + int ret; + + for (addr = 1; addr < ARRAY_SIZE((((struct dg *)0)->cs4245_shadow)); + addr++) { + ret = (op == CS4245_SAVE_TO_SHADOW ? + cs4245_read_spi(chip, addr) : + cs4245_write_spi(chip, addr)); + if (ret < 0) + return ret; + } + return 0; +} + static void cs4245_write(struct oxygen *chip, unsigned int reg, u8 value) { struct dg *data = chip->model_data; diff --git a/sound/pci/oxygen/xonar_dg.h b/sound/pci/oxygen/xonar_dg.h index 0812692..f861a4c 100644 --- a/sound/pci/oxygen/xonar_dg.h +++ b/sound/pci/oxygen/xonar_dg.h @@ -18,7 +18,14 @@ #define PLAYBACK_DST_HP_FP 1 #define PLAYBACK_DST_MULTICH 2
+enum cs4245_shadow_operation { + CS4245_SAVE_TO_SHADOW, + CS4245_LOAD_FROM_SHADOW +}; + struct dg { + /* The shadow copy of the CS4245 register space */ + unsigned char cs4245_shadow[17]; unsigned int output_sel; s8 input_vol[4][2]; unsigned int input_sel;
Roman Volkov wrote:
+int cs4245_shadow_control(struct oxygen *chip, enum cs4245_shadow_operation op) +{
- unsigned char addr;
- int ret;
- for (addr = 1; addr < ARRAY_SIZE((((struct dg *)0)->cs4245_shadow));
addr++) {
This looks ugly (and there's one pair of parentheses too many). Just include the line "struct dg *data = chip->model_data;" in this function.
Regards, Clemens
Changes in GPIO values and sequence at startup, CS4245 initialization changed and simplified, according to datasheets and reverse-engineering information.
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/xonar_dg.c | 88 ++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 55 deletions(-)
diff --git a/sound/pci/oxygen/xonar_dg.c b/sound/pci/oxygen/xonar_dg.c index 0a8ebe1..d6a8c72 100644 --- a/sound/pci/oxygen/xonar_dg.c +++ b/sound/pci/oxygen/xonar_dg.c @@ -147,50 +147,31 @@ static void cs4245_write_cached(struct oxygen *chip, unsigned int reg, u8 value) 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) { 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); + /* Save the initial state: codec version, registers */ + cs4245_shadow_control(chip, CS4245_SAVE_TO_SHADOW); + + /* + * Power up the CODEC internals, enable soft ramp & zero cross, work + * in async. mode, + * Enable aux output from DAC. Invert DAC output as in the Windows + * driver + */ + data->cs4245_shadow[CS4245_POWER_CTRL] = 0; + data->cs4245_shadow[CS4245_SIGNAL_SEL] = CS4245_A_OUT_SEL_DAC | + CS4245_ASYNCH; + data->cs4245_shadow[CS4245_DAC_CTRL_1] = 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; + + cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW); + snd_component_add(chip->card, "CS4245"); }
@@ -200,23 +181,20 @@ static void dg_output_enable(struct oxygen *chip) oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE); }
-static void dg_init(struct oxygen *chip) +void dg_init(struct oxygen *chip) { - struct dg *data = chip->model_data; - - data->output_sel = 0; - data->input_sel = 3; - data->hp_vol_att = 2 * 16; + /* + * The pin XGPIO1 as XGPIO1, gpios 8,7,6,5,1,0 as outputs. + */
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); + 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 void dg_cleanup(struct oxygen *chip) @@ -231,7 +209,7 @@ static void dg_suspend(struct oxygen *chip)
static void dg_resume(struct oxygen *chip) { - cs4245_registers_init(chip); + /*cs4245_registers_init(chip);*/ dg_output_enable(chip); }
Roman Volkov wrote:
Changes in GPIO values and sequence at startup, CS4245 initialization changed and simplified, according to datasheets and reverse-engineering information.
static void cs4245_init(struct oxygen *chip) { 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);
- /* Save the initial state: codec version, registers */
- cs4245_shadow_control(chip, CS4245_SAVE_TO_SHADOW);
- 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;
Please don't remove symbols like "CS4245_DAC_FM_SINGLE | CS4245_DAC_DIF_LJUST"; this would imply that you don't care about these settings.
Why don't you initialize registers like PGA/DAC_A/B?
+void dg_init(struct oxygen *chip) {
- data->output_sel = 0;
- data->input_sel = 3;
- data->hp_vol_att = 2 * 16;
These initializations should not be removed until all the other code that uses them is also removed.
- oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA,
GPIO_INPUT_ROUTE | GPIO_HP_REAR);
- oxygen_write16(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR | GPIO_INPUT_ROUTE);
Why are these bits now initialized to the opposite value?
Regards, Clemens
В Sat, 18 Jan 2014 11:50:52 +0100 Clemens Ladisch clemens@ladisch.de пишет:
Roman Volkov wrote:
Changes in GPIO values and sequence at startup, CS4245 initialization changed and simplified, according to datasheets and reverse-engineering information.
static void cs4245_init(struct oxygen *chip) { 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);
- /* Save the initial state: codec version, registers */
- cs4245_shadow_control(chip, CS4245_SAVE_TO_SHADOW);
- 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;
Please don't remove symbols like "CS4245_DAC_FM_SINGLE | CS4245_DAC_DIF_LJUST"; this would imply that you don't care about these settings.
Okay, will remain these settings. It is better to configure them manually.
Why don't you initialize registers like PGA/DAC_A/B?
If you meant volume control, the mixer performs initialization of these settings. Probably they can be initalized to 30% volume, 100% by default.
- oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA,
GPIO_INPUT_ROUTE | GPIO_HP_REAR);
- oxygen_write16(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR |
GPIO_INPUT_ROUTE);
Why are these bits now initialized to the opposite value?
As I can recall, if GPIOs 5,6 are cleared, the audio input will be inactive and we can't record anything. HP_REAR switches CS4245 to the rear jack. I tried any sequences to minimize clicks\pops. Will try to don't touch the GPIO_HP_REAR, perhaps this reduce the number of relay switches at startup.
Regards, Clemens
When selecting the audio sample rate for CS4245, the MCLK divider should also be changed.
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/xonar_dg.c | 62 ++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 22 deletions(-)
diff --git a/sound/pci/oxygen/xonar_dg.c b/sound/pci/oxygen/xonar_dg.c index d6a8c72..7dcca01 100644 --- a/sound/pci/oxygen/xonar_dg.c +++ b/sound/pci/oxygen/xonar_dg.c @@ -213,36 +213,54 @@ static void dg_resume(struct oxygen *chip) dg_output_enable(chip); }
-static void set_cs4245_dac_params(struct oxygen *chip, +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); + unsigned char dac_ctrl; + unsigned char mclk_freq; + + dac_ctrl = data->cs4245_shadow[CS4245_DAC_CTRL_1] & ~CS4245_DAC_FM_MASK; + mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK1_MASK; + if (params_rate(params) <= 50000) { + dac_ctrl |= CS4245_DAC_FM_SINGLE; + mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT; + } else if (params_rate(params) <= 100000) { + dac_ctrl |= CS4245_DAC_FM_DOUBLE; + mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT; + } else { + dac_ctrl |= CS4245_DAC_FM_QUAD; + mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK1_SHIFT; + } + data->cs4245_shadow[CS4245_DAC_CTRL_1] = dac_ctrl; + data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq; + cs4245_write_spi(chip, CS4245_DAC_CTRL_1); + cs4245_write_spi(chip, CS4245_MCLK_FREQ); }
-static void set_cs4245_adc_params(struct oxygen *chip, +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); + unsigned char adc_ctrl; + unsigned char mclk_freq; + + adc_ctrl = data->cs4245_shadow[CS4245_ADC_CTRL] & ~CS4245_ADC_FM_MASK; + mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK2_MASK; + if (params_rate(params) <= 50000) { + adc_ctrl |= CS4245_ADC_FM_SINGLE; + mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT; + } else if (params_rate(params) <= 100000) { + adc_ctrl |= CS4245_ADC_FM_DOUBLE; + mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT; + } else { + adc_ctrl |= CS4245_ADC_FM_QUAD; + mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK2_SHIFT; + } + data->cs4245_shadow[CS4245_ADC_CTRL] = adc_ctrl; + data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq; + cs4245_write_spi(chip, CS4245_ADC_CTRL); + cs4245_write_spi(chip, CS4245_MCLK_FREQ); }
static inline unsigned int shift_bits(unsigned int value,
When selecting the audio output destinations (headphones, FP headphones, multichannel output), the channel routing should be changed depending on what destination selected. Also unnecessary I2S channels are digitally muted. This function called when the user selects the destination in the ALSA mixer.
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/xonar_dg.c | 35 +++++++++++++++++++++-------------- sound/pci/oxygen/xonar_dg.h | 2 ++ 2 files changed, 23 insertions(+), 14 deletions(-)
diff --git a/sound/pci/oxygen/xonar_dg.c b/sound/pci/oxygen/xonar_dg.c index 7dcca01..3f3201e 100644 --- a/sound/pci/oxygen/xonar_dg.c +++ b/sound/pci/oxygen/xonar_dg.c @@ -274,22 +274,29 @@ static inline unsigned int shift_bits(unsigned int value, return (value >> (shift_from - shift_to)) & mask; }
-static unsigned int adjust_dg_dac_routing(struct oxygen *chip, +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); + struct dg *data = chip->model_data; + unsigned int routing = 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 output_switch_info(struct snd_kcontrol *ctl, diff --git a/sound/pci/oxygen/xonar_dg.h b/sound/pci/oxygen/xonar_dg.h index f861a4c..844a67b 100644 --- a/sound/pci/oxygen/xonar_dg.h +++ b/sound/pci/oxygen/xonar_dg.h @@ -26,6 +26,8 @@ enum cs4245_shadow_operation { struct dg { /* The shadow copy of the CS4245 register space */ unsigned char cs4245_shadow[17]; + /* Output select: headphone/speakers */ + unsigned char pcm_output; unsigned int output_sel; s8 input_vol[4][2]; unsigned int input_sel;
Change the function to read the data from the new shadow buffer.
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/xonar_dg.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/sound/pci/oxygen/xonar_dg.c b/sound/pci/oxygen/xonar_dg.c index 3f3201e..953ef09 100644 --- a/sound/pci/oxygen/xonar_dg.c +++ b/sound/pci/oxygen/xonar_dg.c @@ -625,15 +625,16 @@ static int dg_mixer_init(struct oxygen *chip) return 0; }
-static void dump_cs4245_registers(struct oxygen *chip, - struct snd_info_buffer *buffer) +void dump_cs4245_registers(struct oxygen *chip, + struct snd_info_buffer *buffer) { struct dg *data = chip->model_data; - unsigned int i; + unsigned int addr;
snd_iprintf(buffer, "\nCS4245:"); - for (i = 1; i <= 0x10; ++i) - snd_iprintf(buffer, " %02x", data->cs4245_regs[i]); + cs4245_read_spi(chip, CS4245_INT_STATUS); + for (addr = 1; addr < ARRAY_SIZE(data->cs4245_shadow); addr++) + snd_iprintf(buffer, " %02x", data->cs4245_shadow[addr]); snd_iprintf(buffer, "\n"); }
When putting the card into suspended mode, just disable the audio output. There is no need to do more actions, since PCI slot will be already disabled (AFAIK). When resume, we need to restore erased registers of CS4245 from the memory. When cleanup, just do the same as in suspend.
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/xonar_dg.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/sound/pci/oxygen/xonar_dg.c b/sound/pci/oxygen/xonar_dg.c index 953ef09..80fc099 100644 --- a/sound/pci/oxygen/xonar_dg.c +++ b/sound/pci/oxygen/xonar_dg.c @@ -197,20 +197,21 @@ void dg_init(struct oxygen *chip) GPIO_HP_REAR | GPIO_INPUT_ROUTE); }
-static void dg_cleanup(struct oxygen *chip) +void dg_suspend(struct oxygen *chip) { oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE); }
-static void dg_suspend(struct oxygen *chip) +void dg_resume(struct oxygen *chip) { - dg_cleanup(chip); + cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW); + msleep(2500); + oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE); }
-static void dg_resume(struct oxygen *chip) +void dg_cleanup(struct oxygen *chip) { - /*cs4245_registers_init(chip);*/ - dg_output_enable(chip); + dg_suspend(chip); }
void set_cs4245_dac_params(struct oxygen *chip,
The mixer code and other system structures will come to the separate file. Some unused code also removed.
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/xonar_dg.c | 393 ------------------------------------------- 1 file changed, 393 deletions(-)
diff --git a/sound/pci/oxygen/xonar_dg.c b/sound/pci/oxygen/xonar_dg.c index 80fc099..b0dc16e 100644 --- a/sound/pci/oxygen/xonar_dg.c +++ b/sound/pci/oxygen/xonar_dg.c @@ -124,29 +124,6 @@ int cs4245_shadow_control(struct oxygen *chip, enum cs4245_shadow_operation op) return 0; }
-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_init(struct oxygen *chip) { struct dg *data = chip->model_data; @@ -175,12 +152,6 @@ static void cs4245_init(struct oxygen *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); -} - void dg_init(struct oxygen *chip) { /* @@ -264,17 +235,6 @@ void set_cs4245_adc_params(struct oxygen *chip, cs4245_write_spi(chip, CS4245_MCLK_FREQ); }
-static inline unsigned int shift_bits(unsigned int value, - unsigned int shift_from, - unsigned int shift_to, - unsigned int mask) -{ - if (shift_from < shift_to) - return (value << (shift_to - shift_from)) & mask; - else - return (value >> (shift_from - shift_to)) & mask; -} - unsigned int adjust_dg_dac_routing(struct oxygen *chip, unsigned int play_routing) { @@ -300,332 +260,6 @@ unsigned int adjust_dg_dac_routing(struct oxygen *chip, return routing; }
-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; - - 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); - } - } - 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) -{ - 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" - }; - - return snd_ctl_enum_info(info, 1, 4, names); -} - -static int input_sel_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->input_sel; - mutex_unlock(&chip->mutex); - return 0; -} - -static int input_sel_put(struct snd_kcontrol *ctl, - struct snd_ctl_elem_value *value) -{ - 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; -} - -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) -{ - 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; -} - -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) -{ - 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; -} - void dump_cs4245_registers(struct oxygen *chip, struct snd_info_buffer *buffer) { @@ -638,30 +272,3 @@ void dump_cs4245_registers(struct oxygen *chip, snd_iprintf(buffer, " %02x", data->cs4245_shadow[addr]); 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, - .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, -};
Roman Volkov wrote:
The mixer code and other system structures will come to the separate file.
sound/pci/oxygen/xonar_dg.c | 393 ------------------------------------------- 1 file changed, 393 deletions(-)
This change breaks the driver; you need to merge patches 14..16.
Regards, Clemens
The 'dg' struct changed, contains new values for the mixer controls. Functions declared in the header file to call them from the mixer.
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/xonar_dg.h | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-)
diff --git a/sound/pci/oxygen/xonar_dg.h b/sound/pci/oxygen/xonar_dg.h index 844a67b..636890a 100644 --- a/sound/pci/oxygen/xonar_dg.h +++ b/sound/pci/oxygen/xonar_dg.h @@ -28,13 +28,31 @@ struct dg { unsigned char cs4245_shadow[17]; /* Output select: headphone/speakers */ unsigned char pcm_output; - unsigned int output_sel; - s8 input_vol[4][2]; - unsigned int input_sel; - u8 hp_vol_att; - u8 cs4245_regs[0x11]; + /* Input select: Mic, FP Mic, Line, Aux */ + unsigned char pga_source; + /* Independent capture volume control values */ + char cap_vol[4][2]; + /* Independent mute control values */ + 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_shadow_control(struct oxygen *chip, enum cs4245_shadow_operation op); +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); + extern struct oxygen_model model_xonar_dg;
#endif
The mixer changed. In the new implementation, the headphone volume changes instead of having the 'impedance control'. This allows to use headphones without builtin volume control and without software volume control. Four input sources with independent mute and volume controls, but only one source can be selected.
Signed-off-by: Roman Volkov v1ron@mail.ru --- sound/pci/oxygen/Makefile | 2 +- sound/pci/oxygen/xonar_dg_mixer.c | 483 +++++++++++++++++++++++++++++++++++++ 2 files changed, 484 insertions(+), 1 deletion(-) create mode 100644 sound/pci/oxygen/xonar_dg_mixer.c
diff --git a/sound/pci/oxygen/Makefile b/sound/pci/oxygen/Makefile index 0f87265..8f4c409 100644 --- a/sound/pci/oxygen/Makefile +++ b/sound/pci/oxygen/Makefile @@ -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 --git a/sound/pci/oxygen/xonar_dg_mixer.c b/sound/pci/oxygen/xonar_dg_mixer.c new file mode 100644 index 0000000..39a0da7 --- /dev/null +++ b/sound/pci/oxygen/xonar_dg_mixer.c @@ -0,0 +1,483 @@ +/* + * Mixer controls for the Xonar DG/DGX + * + * Copyright (c) Clemens Ladisch clemens@ladisch.de + * Copyright (c) Roman Volkov v1ron@mail.ru + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver; if not, see http://www.gnu.org/licenses/. + */ + +/* + * 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, +};
Roman Volkov wrote:
This function performs waiting when the SPI bus completes a transaction. Timeout error checking introduced and the timeout increased to 400 from 40.
Why 400? SPI does not allow the codec to delay reads.
- for (count = 100; count > 0; count--) {
if ((oxygen_read8(chip, OXYGEN_SPI_CONTROL) &
OXYGEN_SPI_BUSY) == 0)
return 0;
udelay(4);
--count;
- }
This loop waits for 200 µs.
The SPI transaction will not be finished for the first 30 µs or so, so polling is not needed for that time.
With the wait coming after the transaction, it makes more sense to have the busy check not before but after the udelay.
Regards, Clemens
В Sat, 18 Jan 2014 11:26:49 +0100 Clemens Ladisch clemens@ladisch.de пишет:
This function performs waiting when the SPI bus completes a transaction. Timeout error checking introduced and the timeout increased to 400 from 40.
Why 400? SPI does not allow the codec to delay reads.
Yes. AFAIK, every SPI transaction will take almost the same time, depending on the SPI clock and the number of bits to transfer, regardless of what we're performing, reading or writing.
400 us is just the maximum time, exceeding this will cause an error. Okay, lets calculate, in worst case (3 bytes to transfer, 1280ns clock):
1280 * 8 * 3 = 30270ns, 30,72 us. Perhaps plus some time to initiate the transfer.
400us is greater than 40 by 10 times, but the function never waits 400 us, it always returns earlier than in 40-50 us. I always prefer to increase these timeouts much longer than needed, this guarantees that I never get false-positives and this wait is safe. Even if the error occured, 400us is very small amount of time.
- for (count = 100; count > 0; count--) {
if ((oxygen_read8(chip, OXYGEN_SPI_CONTROL) &
OXYGEN_SPI_BUSY)
== 0)
return 0;
udelay(4);
--count;
- }
This loop waits for 200 µs.
Ah... Thanks. Also why I prefer higher timeouts: http://stackoverflow.com/questions/8352812/linux-kernel-udelay-returns-too-e... Even if a mistake or a bug, this works as needed :) This driver currently works on my PC, if the test succeeded, this decreases my attention :)
The SPI transaction will not be finished for the first 30 µs or so, so polling is not needed for that time.
We may switch the context and force CPU to execute useful code, using something like usleep, msleep. However, this may block the thread for too long time. Please suggest something how to optimize this.
With the wait coming after the transaction, it makes more sense to have the busy check not before but after the udelay.
Okay, this will be:
for (count = 100; count > 0; count--) { udelay(4); if ((oxygen_read8(chip, OXYGEN_SPI_CONTROL) & OXYGEN_SPI_BUSY) == 0) return 0; }
Roman Volkov wrote:
Clemens Ladisch clemens@ladisch.de пишет:
The SPI transaction will not be finished for the first 30 µs or so, so polling is not needed for that time.
We may switch the context and force CPU to execute useful code, using something like usleep, msleep. However, this may block the thread for too long time. Please suggest something how to optimize this.
Actually, SPI transactions could use 2 bytes or a faster clock, so I don't think this needs to be optimized.
With the wait coming after the transaction, it makes more sense to have the busy check not before but after the udelay.
Okay, this will be:
for (count = 100; count > 0; count--) { udelay(4); if ((oxygen_read8(chip, OXYGEN_SPI_CONTROL) & OXYGEN_SPI_BUSY) == 0) return 0; }
Yes.
Regards, Clemens
participants (2)
-
Clemens Ladisch
-
Roman Volkov