diff options
Diffstat (limited to 'sound/pci/oxygen/xonar_dg_mixer.c')
| -rw-r--r-- | sound/pci/oxygen/xonar_dg_mixer.c | 477 | 
1 files changed, 477 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 00000000000..b885dac28a0 --- /dev/null +++ b/sound/pci/oxygen/xonar_dg_mixer.c @@ -0,0 +1,477 @@ +/* + * 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/>. + */ + +#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; +} + +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, +};  | 
