|  | // 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, | 
|  | }; |