468 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Mixer controls for the Xonar DG/DGX4 *5 * Copyright (c) Clemens Ladisch <clemens@ladisch.de>6 * Copyright (c) Roman Volkov <v1ron@mail.ru>7 */8 9#include <linux/pci.h>10#include <linux/delay.h>11#include <sound/control.h>12#include <sound/core.h>13#include <sound/info.h>14#include <sound/pcm.h>15#include <sound/tlv.h>16#include "oxygen.h"17#include "xonar_dg.h"18#include "cs4245.h"19 20/* analog output select */21 22static int output_select_apply(struct oxygen *chip)23{24 struct dg *data = chip->model_data;25 26 data->cs4245_shadow[CS4245_SIGNAL_SEL] &= ~CS4245_A_OUT_SEL_MASK;27 if (data->output_sel == PLAYBACK_DST_HP) {28 /* mute FP (aux output) amplifier, switch rear jack to CS4245 */29 oxygen_set_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR);30 } else if (data->output_sel == PLAYBACK_DST_HP_FP) {31 /*32 * Unmute FP amplifier, switch rear jack to CS4361;33 * I2S channels 2,3,4 should be inactive.34 */35 oxygen_clear_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR);36 data->cs4245_shadow[CS4245_SIGNAL_SEL] |= CS4245_A_OUT_SEL_DAC;37 } else {38 /*39 * 2.0, 4.0, 5.1: switch to CS4361, mute FP amp.,40 * and change playback routing.41 */42 oxygen_clear_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR);43 }44 return cs4245_write_spi(chip, CS4245_SIGNAL_SEL);45}46 47static int output_select_info(struct snd_kcontrol *ctl,48 struct snd_ctl_elem_info *info)49{50 static const char *const names[3] = {51 "Stereo Headphones",52 "Stereo Headphones FP",53 "Multichannel",54 };55 56 return snd_ctl_enum_info(info, 1, 3, names);57}58 59static int output_select_get(struct snd_kcontrol *ctl,60 struct snd_ctl_elem_value *value)61{62 struct oxygen *chip = ctl->private_data;63 struct dg *data = chip->model_data;64 65 mutex_lock(&chip->mutex);66 value->value.enumerated.item[0] = data->output_sel;67 mutex_unlock(&chip->mutex);68 return 0;69}70 71static int output_select_put(struct snd_kcontrol *ctl,72 struct snd_ctl_elem_value *value)73{74 struct oxygen *chip = ctl->private_data;75 struct dg *data = chip->model_data;76 unsigned int new = value->value.enumerated.item[0];77 int changed = 0;78 int ret;79 80 mutex_lock(&chip->mutex);81 if (data->output_sel != new) {82 data->output_sel = new;83 ret = output_select_apply(chip);84 changed = ret >= 0 ? 1 : ret;85 oxygen_update_dac_routing(chip);86 }87 mutex_unlock(&chip->mutex);88 89 return changed;90}91 92/* CS4245 Headphone Channels A&B Volume Control */93 94static int hp_stereo_volume_info(struct snd_kcontrol *ctl,95 struct snd_ctl_elem_info *info)96{97 info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;98 info->count = 2;99 info->value.integer.min = 0;100 info->value.integer.max = 255;101 return 0;102}103 104static int hp_stereo_volume_get(struct snd_kcontrol *ctl,105 struct snd_ctl_elem_value *val)106{107 struct oxygen *chip = ctl->private_data;108 struct dg *data = chip->model_data;109 unsigned int tmp;110 111 mutex_lock(&chip->mutex);112 tmp = (~data->cs4245_shadow[CS4245_DAC_A_CTRL]) & 255;113 val->value.integer.value[0] = tmp;114 tmp = (~data->cs4245_shadow[CS4245_DAC_B_CTRL]) & 255;115 val->value.integer.value[1] = tmp;116 mutex_unlock(&chip->mutex);117 return 0;118}119 120static int hp_stereo_volume_put(struct snd_kcontrol *ctl,121 struct snd_ctl_elem_value *val)122{123 struct oxygen *chip = ctl->private_data;124 struct dg *data = chip->model_data;125 int ret;126 int changed = 0;127 long new1 = val->value.integer.value[0];128 long new2 = val->value.integer.value[1];129 130 if ((new1 > 255) || (new1 < 0) || (new2 > 255) || (new2 < 0))131 return -EINVAL;132 133 mutex_lock(&chip->mutex);134 if ((data->cs4245_shadow[CS4245_DAC_A_CTRL] != ~new1) ||135 (data->cs4245_shadow[CS4245_DAC_B_CTRL] != ~new2)) {136 data->cs4245_shadow[CS4245_DAC_A_CTRL] = ~new1;137 data->cs4245_shadow[CS4245_DAC_B_CTRL] = ~new2;138 ret = cs4245_write_spi(chip, CS4245_DAC_A_CTRL);139 if (ret >= 0)140 ret = cs4245_write_spi(chip, CS4245_DAC_B_CTRL);141 changed = ret >= 0 ? 1 : ret;142 }143 mutex_unlock(&chip->mutex);144 145 return changed;146}147 148/* Headphone Mute */149 150static int hp_mute_get(struct snd_kcontrol *ctl,151 struct snd_ctl_elem_value *val)152{153 struct oxygen *chip = ctl->private_data;154 struct dg *data = chip->model_data;155 156 mutex_lock(&chip->mutex);157 val->value.integer.value[0] =158 !(data->cs4245_shadow[CS4245_DAC_CTRL_1] & CS4245_MUTE_DAC);159 mutex_unlock(&chip->mutex);160 return 0;161}162 163static int hp_mute_put(struct snd_kcontrol *ctl,164 struct snd_ctl_elem_value *val)165{166 struct oxygen *chip = ctl->private_data;167 struct dg *data = chip->model_data;168 int ret;169 int changed;170 171 if (val->value.integer.value[0] > 1)172 return -EINVAL;173 mutex_lock(&chip->mutex);174 data->cs4245_shadow[CS4245_DAC_CTRL_1] &= ~CS4245_MUTE_DAC;175 data->cs4245_shadow[CS4245_DAC_CTRL_1] |=176 (~val->value.integer.value[0] << 2) & CS4245_MUTE_DAC;177 ret = cs4245_write_spi(chip, CS4245_DAC_CTRL_1);178 changed = ret >= 0 ? 1 : ret;179 mutex_unlock(&chip->mutex);180 return changed;181}182 183/* capture volume for all sources */184 185static int input_volume_apply(struct oxygen *chip, char left, char right)186{187 struct dg *data = chip->model_data;188 int ret;189 190 data->cs4245_shadow[CS4245_PGA_A_CTRL] = left;191 data->cs4245_shadow[CS4245_PGA_B_CTRL] = right;192 ret = cs4245_write_spi(chip, CS4245_PGA_A_CTRL);193 if (ret < 0)194 return ret;195 return cs4245_write_spi(chip, CS4245_PGA_B_CTRL);196}197 198static int input_vol_info(struct snd_kcontrol *ctl,199 struct snd_ctl_elem_info *info)200{201 info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;202 info->count = 2;203 info->value.integer.min = 2 * -12;204 info->value.integer.max = 2 * 12;205 return 0;206}207 208static int input_vol_get(struct snd_kcontrol *ctl,209 struct snd_ctl_elem_value *value)210{211 struct oxygen *chip = ctl->private_data;212 struct dg *data = chip->model_data;213 unsigned int idx = ctl->private_value;214 215 mutex_lock(&chip->mutex);216 value->value.integer.value[0] = data->input_vol[idx][0];217 value->value.integer.value[1] = data->input_vol[idx][1];218 mutex_unlock(&chip->mutex);219 return 0;220}221 222static int input_vol_put(struct snd_kcontrol *ctl,223 struct snd_ctl_elem_value *value)224{225 struct oxygen *chip = ctl->private_data;226 struct dg *data = chip->model_data;227 unsigned int idx = ctl->private_value;228 int changed = 0;229 int ret = 0;230 231 if (value->value.integer.value[0] < 2 * -12 ||232 value->value.integer.value[0] > 2 * 12 ||233 value->value.integer.value[1] < 2 * -12 ||234 value->value.integer.value[1] > 2 * 12)235 return -EINVAL;236 mutex_lock(&chip->mutex);237 changed = data->input_vol[idx][0] != value->value.integer.value[0] ||238 data->input_vol[idx][1] != value->value.integer.value[1];239 if (changed) {240 data->input_vol[idx][0] = value->value.integer.value[0];241 data->input_vol[idx][1] = value->value.integer.value[1];242 if (idx == data->input_sel) {243 ret = input_volume_apply(chip,244 data->input_vol[idx][0],245 data->input_vol[idx][1]);246 }247 changed = ret >= 0 ? 1 : ret;248 }249 mutex_unlock(&chip->mutex);250 return changed;251}252 253/* Capture Source */254 255static int input_source_apply(struct oxygen *chip)256{257 struct dg *data = chip->model_data;258 259 data->cs4245_shadow[CS4245_ANALOG_IN] &= ~CS4245_SEL_MASK;260 if (data->input_sel == CAPTURE_SRC_FP_MIC)261 data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_2;262 else if (data->input_sel == CAPTURE_SRC_LINE)263 data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_4;264 else if (data->input_sel != CAPTURE_SRC_MIC)265 data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_1;266 return cs4245_write_spi(chip, CS4245_ANALOG_IN);267}268 269static int input_sel_info(struct snd_kcontrol *ctl,270 struct snd_ctl_elem_info *info)271{272 static const char *const names[4] = {273 "Mic", "Front Mic", "Line", "Aux"274 };275 276 return snd_ctl_enum_info(info, 1, 4, names);277}278 279static int input_sel_get(struct snd_kcontrol *ctl,280 struct snd_ctl_elem_value *value)281{282 struct oxygen *chip = ctl->private_data;283 struct dg *data = chip->model_data;284 285 mutex_lock(&chip->mutex);286 value->value.enumerated.item[0] = data->input_sel;287 mutex_unlock(&chip->mutex);288 return 0;289}290 291static int input_sel_put(struct snd_kcontrol *ctl,292 struct snd_ctl_elem_value *value)293{294 struct oxygen *chip = ctl->private_data;295 struct dg *data = chip->model_data;296 int changed;297 int ret;298 299 if (value->value.enumerated.item[0] > 3)300 return -EINVAL;301 302 mutex_lock(&chip->mutex);303 changed = value->value.enumerated.item[0] != data->input_sel;304 if (changed) {305 data->input_sel = value->value.enumerated.item[0];306 307 ret = input_source_apply(chip);308 if (ret >= 0)309 ret = input_volume_apply(chip,310 data->input_vol[data->input_sel][0],311 data->input_vol[data->input_sel][1]);312 changed = ret >= 0 ? 1 : ret;313 }314 mutex_unlock(&chip->mutex);315 return changed;316}317 318/* ADC high-pass filter */319 320static int hpf_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)321{322 static const char *const names[2] = { "Active", "Frozen" };323 324 return snd_ctl_enum_info(info, 1, 2, names);325}326 327static int hpf_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)328{329 struct oxygen *chip = ctl->private_data;330 struct dg *data = chip->model_data;331 332 value->value.enumerated.item[0] =333 !!(data->cs4245_shadow[CS4245_ADC_CTRL] & CS4245_HPF_FREEZE);334 return 0;335}336 337static int hpf_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)338{339 struct oxygen *chip = ctl->private_data;340 struct dg *data = chip->model_data;341 u8 reg;342 int changed;343 344 mutex_lock(&chip->mutex);345 reg = data->cs4245_shadow[CS4245_ADC_CTRL] & ~CS4245_HPF_FREEZE;346 if (value->value.enumerated.item[0])347 reg |= CS4245_HPF_FREEZE;348 changed = reg != data->cs4245_shadow[CS4245_ADC_CTRL];349 if (changed) {350 data->cs4245_shadow[CS4245_ADC_CTRL] = reg;351 cs4245_write_spi(chip, CS4245_ADC_CTRL);352 }353 mutex_unlock(&chip->mutex);354 return changed;355}356 357#define INPUT_VOLUME(xname, index) { \358 .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \359 .name = xname, \360 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \361 SNDRV_CTL_ELEM_ACCESS_TLV_READ, \362 .info = input_vol_info, \363 .get = input_vol_get, \364 .put = input_vol_put, \365 .tlv = { .p = pga_db_scale }, \366 .private_value = index, \367}368static const DECLARE_TLV_DB_MINMAX(hp_db_scale, -12550, 0);369static const DECLARE_TLV_DB_MINMAX(pga_db_scale, -1200, 1200);370static const struct snd_kcontrol_new dg_controls[] = {371 {372 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,373 .name = "Analog Output Playback Enum",374 .info = output_select_info,375 .get = output_select_get,376 .put = output_select_put,377 },378 {379 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,380 .name = "Headphone Playback Volume",381 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |382 SNDRV_CTL_ELEM_ACCESS_TLV_READ,383 .info = hp_stereo_volume_info,384 .get = hp_stereo_volume_get,385 .put = hp_stereo_volume_put,386 .tlv = { .p = hp_db_scale, },387 },388 {389 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,390 .name = "Headphone Playback Switch",391 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,392 .info = snd_ctl_boolean_mono_info,393 .get = hp_mute_get,394 .put = hp_mute_put,395 },396 INPUT_VOLUME("Mic Capture Volume", CAPTURE_SRC_MIC),397 INPUT_VOLUME("Front Mic Capture Volume", CAPTURE_SRC_FP_MIC),398 INPUT_VOLUME("Line Capture Volume", CAPTURE_SRC_LINE),399 INPUT_VOLUME("Aux Capture Volume", CAPTURE_SRC_AUX),400 {401 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,402 .name = "Capture Source",403 .info = input_sel_info,404 .get = input_sel_get,405 .put = input_sel_put,406 },407 {408 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,409 .name = "ADC High-pass Filter Capture Enum",410 .info = hpf_info,411 .get = hpf_get,412 .put = hpf_put,413 },414};415 416static int dg_control_filter(struct snd_kcontrol_new *template)417{418 if (!strncmp(template->name, "Master Playback ", 16))419 return 1;420 return 0;421}422 423static int dg_mixer_init(struct oxygen *chip)424{425 unsigned int i;426 int err;427 428 output_select_apply(chip);429 input_source_apply(chip);430 oxygen_update_dac_routing(chip);431 432 for (i = 0; i < ARRAY_SIZE(dg_controls); ++i) {433 err = snd_ctl_add(chip->card,434 snd_ctl_new1(&dg_controls[i], chip));435 if (err < 0)436 return err;437 }438 439 return 0;440}441 442const struct oxygen_model model_xonar_dg = {443 .longname = "C-Media Oxygen HD Audio",444 .chip = "CMI8786",445 .init = dg_init,446 .control_filter = dg_control_filter,447 .mixer_init = dg_mixer_init,448 .cleanup = dg_cleanup,449 .suspend = dg_suspend,450 .resume = dg_resume,451 .set_dac_params = set_cs4245_dac_params,452 .set_adc_params = set_cs4245_adc_params,453 .adjust_dac_routing = adjust_dg_dac_routing,454 .dump_registers = dump_cs4245_registers,455 .model_data_size = sizeof(struct dg),456 .device_config = PLAYBACK_0_TO_I2S |457 PLAYBACK_1_TO_SPDIF |458 CAPTURE_0_FROM_I2S_1 |459 CAPTURE_1_FROM_SPDIF,460 .dac_channels_pcm = 6,461 .dac_channels_mixer = 0,462 .function_flags = OXYGEN_FUNCTION_SPI,463 .dac_mclks = OXYGEN_MCLKS(256, 128, 128),464 .adc_mclks = OXYGEN_MCLKS(256, 128, 128),465 .dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,466 .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,467};468