summaryrefslogtreecommitdiffstats
path: root/sound/pci/oxygen/xonar_dg_mixer.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/pci/oxygen/xonar_dg_mixer.c')
-rw-r--r--sound/pci/oxygen/xonar_dg_mixer.c467
1 files changed, 467 insertions, 0 deletions
diff --git a/sound/pci/oxygen/xonar_dg_mixer.c b/sound/pci/oxygen/xonar_dg_mixer.c
new file mode 100644
index 000000000..198588562
--- /dev/null
+++ b/sound/pci/oxygen/xonar_dg_mixer.c
@@ -0,0 +1,467 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Mixer controls for the Xonar DG/DGX
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Copyright (c) Roman Volkov <v1ron@mail.ru>
+ */
+
+#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"
+
+/* analog output select */
+
+static int output_select_apply(struct oxygen *chip)
+{
+ struct dg *data = chip->model_data;
+
+ data->cs4245_shadow[CS4245_SIGNAL_SEL] &= ~CS4245_A_OUT_SEL_MASK;
+ if (data->output_sel == 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->output_sel == PLAYBACK_DST_HP_FP) {
+ /*
+ * Unmute FP amplifier, switch rear jack to CS4361;
+ * I2S channels 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.
+ */
+ oxygen_clear_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR);
+ }
+ return cs4245_write_spi(chip, CS4245_SIGNAL_SEL);
+}
+
+static int output_select_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 output_select_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_select_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 new = value->value.enumerated.item[0];
+ int changed = 0;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+ if (data->output_sel != new) {
+ data->output_sel = new;
+ ret = output_select_apply(chip);
+ changed = ret >= 0 ? 1 : ret;
+ oxygen_update_dac_routing(chip);
+ }
+ mutex_unlock(&chip->mutex);
+
+ return changed;
+}
+
+/* CS4245 Headphone Channels A&B Volume Control */
+
+static int hp_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 hp_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 hp_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;
+ int ret;
+ int changed = 0;
+ long new1 = val->value.integer.value[0];
+ long new2 = val->value.integer.value[1];
+
+ if ((new1 > 255) || (new1 < 0) || (new2 > 255) || (new2 < 0))
+ return -EINVAL;
+
+ mutex_lock(&chip->mutex);
+ if ((data->cs4245_shadow[CS4245_DAC_A_CTRL] != ~new1) ||
+ (data->cs4245_shadow[CS4245_DAC_B_CTRL] != ~new2)) {
+ data->cs4245_shadow[CS4245_DAC_A_CTRL] = ~new1;
+ data->cs4245_shadow[CS4245_DAC_B_CTRL] = ~new2;
+ ret = cs4245_write_spi(chip, CS4245_DAC_A_CTRL);
+ if (ret >= 0)
+ ret = cs4245_write_spi(chip, CS4245_DAC_B_CTRL);
+ changed = ret >= 0 ? 1 : ret;
+ }
+ mutex_unlock(&chip->mutex);
+
+ return changed;
+}
+
+/* Headphone Mute */
+
+static int hp_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);
+ mutex_unlock(&chip->mutex);
+ return 0;
+}
+
+static int hp_mute_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;
+ int changed;
+
+ 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;
+ ret = cs4245_write_spi(chip, CS4245_DAC_CTRL_1);
+ changed = ret >= 0 ? 1 : ret;
+ mutex_unlock(&chip->mutex);
+ return changed;
+}
+
+/* capture volume for all sources */
+
+static int input_volume_apply(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 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;
+ int ret = 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) {
+ ret = input_volume_apply(chip,
+ data->input_vol[idx][0],
+ data->input_vol[idx][1]);
+ }
+ changed = ret >= 0 ? 1 : ret;
+ }
+ mutex_unlock(&chip->mutex);
+ return changed;
+}
+
+/* Capture Source */
+
+static int input_source_apply(struct oxygen *chip)
+{
+ struct dg *data = chip->model_data;
+
+ data->cs4245_shadow[CS4245_ANALOG_IN] &= ~CS4245_SEL_MASK;
+ if (data->input_sel == CAPTURE_SRC_FP_MIC)
+ data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_2;
+ else if (data->input_sel == CAPTURE_SRC_LINE)
+ data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_4;
+ else if (data->input_sel != CAPTURE_SRC_MIC)
+ data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_1;
+ return cs4245_write_spi(chip, CS4245_ANALOG_IN);
+}
+
+static int input_sel_info(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_info *info)
+{
+ static const char *const names[4] = {
+ "Mic", "Front Mic", "Line", "Aux"
+ };
+
+ 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)
+{
+ struct oxygen *chip = ctl->private_data;
+ struct dg *data = chip->model_data;
+ int changed;
+ int ret;
+
+ 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];
+
+ ret = input_source_apply(chip);
+ if (ret >= 0)
+ ret = input_volume_apply(chip,
+ data->input_vol[data->input_sel][0],
+ data->input_vol[data->input_sel][1]);
+ changed = ret >= 0 ? 1 : ret;
+ }
+ mutex_unlock(&chip->mutex);
+ return changed;
+}
+
+/* ADC high-pass filter */
+
+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_shadow[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_shadow[CS4245_ADC_CTRL] & ~CS4245_HPF_FREEZE;
+ if (value->value.enumerated.item[0])
+ reg |= CS4245_HPF_FREEZE;
+ changed = reg != data->cs4245_shadow[CS4245_ADC_CTRL];
+ if (changed) {
+ data->cs4245_shadow[CS4245_ADC_CTRL] = reg;
+ cs4245_write_spi(chip, CS4245_ADC_CTRL);
+ }
+ mutex_unlock(&chip->mutex);
+ return changed;
+}
+
+#define INPUT_VOLUME(xname, index) { \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = xname, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+ .info = input_vol_info, \
+ .get = input_vol_get, \
+ .put = input_vol_put, \
+ .tlv = { .p = pga_db_scale }, \
+ .private_value = index, \
+}
+static const DECLARE_TLV_DB_MINMAX(hp_db_scale, -12550, 0);
+static const DECLARE_TLV_DB_MINMAX(pga_db_scale, -1200, 1200);
+static const struct snd_kcontrol_new dg_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Output Playback Enum",
+ .info = output_select_info,
+ .get = output_select_get,
+ .put = output_select_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Headphone Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = hp_stereo_volume_info,
+ .get = hp_stereo_volume_get,
+ .put = hp_stereo_volume_put,
+ .tlv = { .p = 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 = hp_mute_get,
+ .put = hp_mute_put,
+ },
+ INPUT_VOLUME("Mic Capture Volume", CAPTURE_SRC_MIC),
+ INPUT_VOLUME("Front Mic Capture Volume", CAPTURE_SRC_FP_MIC),
+ INPUT_VOLUME("Line Capture Volume", CAPTURE_SRC_LINE),
+ INPUT_VOLUME("Aux Capture Volume", CAPTURE_SRC_AUX),
+ {
+ .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;
+
+ output_select_apply(chip);
+ input_source_apply(chip);
+ oxygen_update_dac_routing(chip);
+
+ 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;
+}
+
+const 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_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,
+};