Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / sound / pci / oxygen / xonar_dg.c
diff --git a/kernel/sound/pci/oxygen/xonar_dg.c b/kernel/sound/pci/oxygen/xonar_dg.c
new file mode 100644 (file)
index 0000000..4cf3200
--- /dev/null
@@ -0,0 +1,295 @@
+/*
+ * card driver for the Xonar DG/DGX
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Copyright (c) Roman Volkov <v1ron@mail.ru>
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Xonar DG/DGX
+ * ------------
+ *
+ * CS4245 and CS4361 both will mute all outputs if any clock ratio
+ * is invalid.
+ *
+ * CMI8788:
+ *
+ *   SPI 0 -> CS4245
+ *
+ *   Playback:
+ *   I²S 1 -> CS4245
+ *   I²S 2 -> CS4361 (center/LFE)
+ *   I²S 3 -> CS4361 (surround)
+ *   I²S 4 -> CS4361 (front)
+ *   Capture:
+ *   I²S ADC 1 <- CS4245
+ *
+ *   GPIO 3 <- ?
+ *   GPIO 4 <- headphone detect
+ *   GPIO 5 -> enable ADC analog circuit for the left channel
+ *   GPIO 6 -> enable ADC analog circuit for the right channel
+ *   GPIO 7 -> switch green rear output jack between CS4245 and and the first
+ *             channel of CS4361 (mechanical relay)
+ *   GPIO 8 -> enable output to speakers
+ *
+ * CS4245:
+ *
+ *   input 0 <- mic
+ *   input 1 <- aux
+ *   input 2 <- front mic
+ *   input 4 <- line
+ *   DAC out -> headphones
+ *   aux out -> front panel headphones
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+#include "oxygen.h"
+#include "xonar_dg.h"
+#include "cs4245.h"
+
+int cs4245_write_spi(struct oxygen *chip, u8 reg)
+{
+       struct dg *data = chip->model_data;
+       unsigned int packet;
+
+       packet = reg << 8;
+       packet |= (CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 16;
+       packet |= data->cs4245_shadow[reg];
+
+       return oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+                               OXYGEN_SPI_DATA_LENGTH_3 |
+                               OXYGEN_SPI_CLOCK_1280 |
+                               (0 << OXYGEN_SPI_CODEC_SHIFT) |
+                               OXYGEN_SPI_CEN_LATCH_CLOCK_HI,
+                               packet);
+}
+
+int cs4245_read_spi(struct oxygen *chip, u8 addr)
+{
+       struct dg *data = chip->model_data;
+       int ret;
+
+       ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+               OXYGEN_SPI_DATA_LENGTH_2 |
+               OXYGEN_SPI_CEN_LATCH_CLOCK_HI |
+               OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT),
+               ((CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 8) | addr);
+       if (ret < 0)
+               return ret;
+
+       ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+               OXYGEN_SPI_DATA_LENGTH_2 |
+               OXYGEN_SPI_CEN_LATCH_CLOCK_HI |
+               OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT),
+               (CS4245_SPI_ADDRESS | CS4245_SPI_READ) << 8);
+       if (ret < 0)
+               return ret;
+
+       data->cs4245_shadow[addr] = oxygen_read8(chip, OXYGEN_SPI_DATA1);
+
+       return 0;
+}
+
+int cs4245_shadow_control(struct oxygen *chip, enum cs4245_shadow_operation op)
+{
+       struct dg *data = chip->model_data;
+       unsigned char addr;
+       int ret;
+
+       for (addr = 1; addr < ARRAY_SIZE(data->cs4245_shadow); addr++) {
+               ret = (op == CS4245_SAVE_TO_SHADOW ?
+                       cs4245_read_spi(chip, addr) :
+                       cs4245_write_spi(chip, addr));
+               if (ret < 0)
+                       return ret;
+       }
+       return 0;
+}
+
+static void cs4245_init(struct oxygen *chip)
+{
+       struct dg *data = chip->model_data;
+
+       /* save the initial state: codec version, registers */
+       cs4245_shadow_control(chip, CS4245_SAVE_TO_SHADOW);
+
+       /*
+        * Power up the CODEC internals, enable soft ramp & zero cross, work in
+        * async. mode, enable aux output from DAC. Invert DAC output as in the
+        * Windows driver.
+        */
+       data->cs4245_shadow[CS4245_POWER_CTRL] = 0;
+       data->cs4245_shadow[CS4245_SIGNAL_SEL] =
+               CS4245_A_OUT_SEL_DAC | CS4245_ASYNCH;
+       data->cs4245_shadow[CS4245_DAC_CTRL_1] =
+               CS4245_DAC_FM_SINGLE | CS4245_DAC_DIF_LJUST;
+       data->cs4245_shadow[CS4245_DAC_CTRL_2] =
+               CS4245_DAC_SOFT | CS4245_DAC_ZERO | CS4245_INVERT_DAC;
+       data->cs4245_shadow[CS4245_ADC_CTRL] =
+               CS4245_ADC_FM_SINGLE | CS4245_ADC_DIF_LJUST;
+       data->cs4245_shadow[CS4245_ANALOG_IN] =
+               CS4245_PGA_SOFT | CS4245_PGA_ZERO;
+       data->cs4245_shadow[CS4245_PGA_B_CTRL] = 0;
+       data->cs4245_shadow[CS4245_PGA_A_CTRL] = 0;
+       data->cs4245_shadow[CS4245_DAC_A_CTRL] = 8;
+       data->cs4245_shadow[CS4245_DAC_B_CTRL] = 8;
+
+       cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW);
+       snd_component_add(chip->card, "CS4245");
+}
+
+void dg_init(struct oxygen *chip)
+{
+       struct dg *data = chip->model_data;
+
+       data->output_sel = PLAYBACK_DST_HP_FP;
+       data->input_sel = CAPTURE_SRC_MIC;
+
+       cs4245_init(chip);
+       oxygen_write16(chip, OXYGEN_GPIO_CONTROL,
+                      GPIO_OUTPUT_ENABLE | GPIO_HP_REAR | GPIO_INPUT_ROUTE);
+       /* anti-pop delay, wait some time before enabling the output */
+       msleep(2500);
+       oxygen_write16(chip, OXYGEN_GPIO_DATA,
+                      GPIO_OUTPUT_ENABLE | GPIO_INPUT_ROUTE);
+}
+
+void dg_cleanup(struct oxygen *chip)
+{
+       oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE);
+}
+
+void dg_suspend(struct oxygen *chip)
+{
+       dg_cleanup(chip);
+}
+
+void dg_resume(struct oxygen *chip)
+{
+       cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW);
+       msleep(2500);
+       oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE);
+}
+
+void set_cs4245_dac_params(struct oxygen *chip,
+                                 struct snd_pcm_hw_params *params)
+{
+       struct dg *data = chip->model_data;
+       unsigned char dac_ctrl;
+       unsigned char mclk_freq;
+
+       dac_ctrl = data->cs4245_shadow[CS4245_DAC_CTRL_1] & ~CS4245_DAC_FM_MASK;
+       mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK1_MASK;
+       if (params_rate(params) <= 50000) {
+               dac_ctrl |= CS4245_DAC_FM_SINGLE;
+               mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT;
+       } else if (params_rate(params) <= 100000) {
+               dac_ctrl |= CS4245_DAC_FM_DOUBLE;
+               mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT;
+       } else {
+               dac_ctrl |= CS4245_DAC_FM_QUAD;
+               mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK1_SHIFT;
+       }
+       data->cs4245_shadow[CS4245_DAC_CTRL_1] = dac_ctrl;
+       data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq;
+       cs4245_write_spi(chip, CS4245_DAC_CTRL_1);
+       cs4245_write_spi(chip, CS4245_MCLK_FREQ);
+}
+
+void set_cs4245_adc_params(struct oxygen *chip,
+                                 struct snd_pcm_hw_params *params)
+{
+       struct dg *data = chip->model_data;
+       unsigned char adc_ctrl;
+       unsigned char mclk_freq;
+
+       adc_ctrl = data->cs4245_shadow[CS4245_ADC_CTRL] & ~CS4245_ADC_FM_MASK;
+       mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK2_MASK;
+       if (params_rate(params) <= 50000) {
+               adc_ctrl |= CS4245_ADC_FM_SINGLE;
+               mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT;
+       } else if (params_rate(params) <= 100000) {
+               adc_ctrl |= CS4245_ADC_FM_DOUBLE;
+               mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT;
+       } else {
+               adc_ctrl |= CS4245_ADC_FM_QUAD;
+               mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK2_SHIFT;
+       }
+       data->cs4245_shadow[CS4245_ADC_CTRL] = adc_ctrl;
+       data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq;
+       cs4245_write_spi(chip, CS4245_ADC_CTRL);
+       cs4245_write_spi(chip, CS4245_MCLK_FREQ);
+}
+
+static inline unsigned int shift_bits(unsigned int value,
+                                     unsigned int shift_from,
+                                     unsigned int shift_to,
+                                     unsigned int mask)
+{
+       if (shift_from < shift_to)
+               return (value << (shift_to - shift_from)) & mask;
+       else
+               return (value >> (shift_from - shift_to)) & mask;
+}
+
+unsigned int adjust_dg_dac_routing(struct oxygen *chip,
+                                         unsigned int play_routing)
+{
+       struct dg *data = chip->model_data;
+
+       switch (data->output_sel) {
+       case PLAYBACK_DST_HP:
+       case PLAYBACK_DST_HP_FP:
+               oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING,
+                       OXYGEN_PLAY_MUTE23 | OXYGEN_PLAY_MUTE45 |
+                       OXYGEN_PLAY_MUTE67, OXYGEN_PLAY_MUTE_MASK);
+               break;
+       case PLAYBACK_DST_MULTICH:
+               oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING,
+                       OXYGEN_PLAY_MUTE01, OXYGEN_PLAY_MUTE_MASK);
+               break;
+       }
+       return (play_routing & OXYGEN_PLAY_DAC0_SOURCE_MASK) |
+              shift_bits(play_routing,
+                         OXYGEN_PLAY_DAC2_SOURCE_SHIFT,
+                         OXYGEN_PLAY_DAC1_SOURCE_SHIFT,
+                         OXYGEN_PLAY_DAC1_SOURCE_MASK) |
+              shift_bits(play_routing,
+                         OXYGEN_PLAY_DAC1_SOURCE_SHIFT,
+                         OXYGEN_PLAY_DAC2_SOURCE_SHIFT,
+                         OXYGEN_PLAY_DAC2_SOURCE_MASK) |
+              shift_bits(play_routing,
+                         OXYGEN_PLAY_DAC0_SOURCE_SHIFT,
+                         OXYGEN_PLAY_DAC3_SOURCE_SHIFT,
+                         OXYGEN_PLAY_DAC3_SOURCE_MASK);
+}
+
+void dump_cs4245_registers(struct oxygen *chip,
+                                 struct snd_info_buffer *buffer)
+{
+       struct dg *data = chip->model_data;
+       unsigned int addr;
+
+       snd_iprintf(buffer, "\nCS4245:");
+       cs4245_read_spi(chip, CS4245_INT_STATUS);
+       for (addr = 1; addr < ARRAY_SIZE(data->cs4245_shadow); addr++)
+               snd_iprintf(buffer, " %02x", data->cs4245_shadow[addr]);
+       snd_iprintf(buffer, "\n");
+}