linux/sound/pci/echoaudio/echoaudio.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 *  ALSA driver for Echoaudio soundcards.
 *  Copyright (C) 2003-2004 Giuliano Pochini <[email protected]>
 *  Copyright (C) 2020 Mark Hills <[email protected]>
 */

#include <linux/module.h>

MODULE_AUTHOR();
MODULE_LICENSE();
MODULE_DESCRIPTION();
MODULE_DEVICE_TABLE(pci, snd_echo_ids);

static int index[SNDRV_CARDS] =;
static char *id[SNDRV_CARDS] =;
static bool enable[SNDRV_CARDS] =;

module_param_array();
MODULE_PARM_DESC();
module_param_array();
MODULE_PARM_DESC();
module_param_array();
MODULE_PARM_DESC();

static const unsigned int channels_list[10] =;
static const DECLARE_TLV_DB_SCALE(db_scale_output_gain, -12800, 100, 1);



static int get_firmware(const struct firmware **fw_entry,
			struct echoaudio *chip, const short fw_index)
{}



static void free_firmware(const struct firmware *fw_entry,
			  struct echoaudio *chip)
{}



static void free_firmware_cache(struct echoaudio *chip)
{}



/******************************************************************************
	PCM interface
******************************************************************************/

static void audiopipe_free(struct snd_pcm_runtime *runtime)
{}



static int hw_rule_capture_format_by_channels(struct snd_pcm_hw_params *params,
					      struct snd_pcm_hw_rule *rule)
{}



static int hw_rule_capture_channels_by_format(struct snd_pcm_hw_params *params,
					      struct snd_pcm_hw_rule *rule)
{}



static int hw_rule_playback_format_by_channels(struct snd_pcm_hw_params *params,
					       struct snd_pcm_hw_rule *rule)
{}



static int hw_rule_playback_channels_by_format(struct snd_pcm_hw_params *params,
					       struct snd_pcm_hw_rule *rule)
{}



/* Since the sample rate is a global setting, do allow the user to change the
sample rate only if there is only one pcm device open. */
static int hw_rule_sample_rate(struct snd_pcm_hw_params *params,
			       struct snd_pcm_hw_rule *rule)
{}


static int pcm_open(struct snd_pcm_substream *substream,
		    signed char max_channels)
{}



static int pcm_analog_in_open(struct snd_pcm_substream *substream)
{}



static int pcm_analog_out_open(struct snd_pcm_substream *substream)
{}



#ifdef ECHOCARD_HAS_DIGITAL_IO

static int pcm_digital_in_open(struct snd_pcm_substream *substream)
{
	struct echoaudio *chip = snd_pcm_substream_chip(substream);
	int err, max_channels;

	max_channels = num_digital_busses_in(chip) - substream->number;
	mutex_lock(&chip->mode_mutex);
	if (chip->digital_mode == DIGITAL_MODE_ADAT)
		err = pcm_open(substream, max_channels);
	else	/* If the card has ADAT, subtract the 6 channels
		 * that S/PDIF doesn't have
		 */
		err = pcm_open(substream, max_channels - ECHOCARD_HAS_ADAT);

	if (err < 0)
		goto din_exit;

	err = snd_pcm_hw_rule_add(substream->runtime, 0,
				  SNDRV_PCM_HW_PARAM_CHANNELS,
				  hw_rule_capture_channels_by_format, NULL,
				  SNDRV_PCM_HW_PARAM_FORMAT, -1);
	if (err < 0)
		goto din_exit;
	err = snd_pcm_hw_rule_add(substream->runtime, 0,
				  SNDRV_PCM_HW_PARAM_FORMAT,
				  hw_rule_capture_format_by_channels, NULL,
				  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
	if (err < 0)
		goto din_exit;

din_exit:
	mutex_unlock(&chip->mode_mutex);
	return err;
}



#ifndef ECHOCARD_HAS_VMIXER	/* See the note in snd_echo_new_pcm() */

static int pcm_digital_out_open(struct snd_pcm_substream *substream)
{
	struct echoaudio *chip = snd_pcm_substream_chip(substream);
	int err, max_channels;

	max_channels = num_digital_busses_out(chip) - substream->number;
	mutex_lock(&chip->mode_mutex);
	if (chip->digital_mode == DIGITAL_MODE_ADAT)
		err = pcm_open(substream, max_channels);
	else	/* If the card has ADAT, subtract the 6 channels
		 * that S/PDIF doesn't have
		 */
		err = pcm_open(substream, max_channels - ECHOCARD_HAS_ADAT);

	if (err < 0)
		goto dout_exit;

	err = snd_pcm_hw_rule_add(substream->runtime, 0,
				  SNDRV_PCM_HW_PARAM_CHANNELS,
				  hw_rule_playback_channels_by_format,
				  NULL, SNDRV_PCM_HW_PARAM_FORMAT,
				  -1);
	if (err < 0)
		goto dout_exit;
	err = snd_pcm_hw_rule_add(substream->runtime, 0,
				  SNDRV_PCM_HW_PARAM_FORMAT,
				  hw_rule_playback_format_by_channels,
				  NULL, SNDRV_PCM_HW_PARAM_CHANNELS,
				  -1);
	if (err < 0)
		goto dout_exit;

dout_exit:
	mutex_unlock(&chip->mode_mutex);
	return err;
}

#endif /* !ECHOCARD_HAS_VMIXER */

#endif /* ECHOCARD_HAS_DIGITAL_IO */



static int pcm_close(struct snd_pcm_substream *substream)
{}



/* Channel allocation and scatter-gather list setup */
static int init_engine(struct snd_pcm_substream *substream,
		       struct snd_pcm_hw_params *hw_params,
		       int pipe_index, int interleave)
{}



static int pcm_analog_in_hw_params(struct snd_pcm_substream *substream,
				   struct snd_pcm_hw_params *hw_params)
{}



static int pcm_analog_out_hw_params(struct snd_pcm_substream *substream,
				    struct snd_pcm_hw_params *hw_params)
{}



#ifdef ECHOCARD_HAS_DIGITAL_IO

static int pcm_digital_in_hw_params(struct snd_pcm_substream *substream,
				    struct snd_pcm_hw_params *hw_params)
{
	struct echoaudio *chip = snd_pcm_substream_chip(substream);

	return init_engine(substream, hw_params, px_digital_in(chip) +
			substream->number, params_channels(hw_params));
}



#ifndef ECHOCARD_HAS_VMIXER	/* See the note in snd_echo_new_pcm() */
static int pcm_digital_out_hw_params(struct snd_pcm_substream *substream,
				     struct snd_pcm_hw_params *hw_params)
{
	struct echoaudio *chip = snd_pcm_substream_chip(substream);

	return init_engine(substream, hw_params, px_digital_out(chip) +
			substream->number, params_channels(hw_params));
}
#endif /* !ECHOCARD_HAS_VMIXER */

#endif /* ECHOCARD_HAS_DIGITAL_IO */



static int pcm_hw_free(struct snd_pcm_substream *substream)
{}



static int pcm_prepare(struct snd_pcm_substream *substream)
{}



static int pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{}



static snd_pcm_uframes_t pcm_pointer(struct snd_pcm_substream *substream)
{}



/* pcm *_ops structures */
static const struct snd_pcm_ops analog_playback_ops =;
static const struct snd_pcm_ops analog_capture_ops =;
#ifdef ECHOCARD_HAS_DIGITAL_IO
#ifndef ECHOCARD_HAS_VMIXER
static const struct snd_pcm_ops digital_playback_ops = {
	.open = pcm_digital_out_open,
	.close = pcm_close,
	.hw_params = pcm_digital_out_hw_params,
	.hw_free = pcm_hw_free,
	.prepare = pcm_prepare,
	.trigger = pcm_trigger,
	.pointer = pcm_pointer,
};
#endif /* !ECHOCARD_HAS_VMIXER */
static const struct snd_pcm_ops digital_capture_ops = {
	.open = pcm_digital_in_open,
	.close = pcm_close,
	.hw_params = pcm_digital_in_hw_params,
	.hw_free = pcm_hw_free,
	.prepare = pcm_prepare,
	.trigger = pcm_trigger,
	.pointer = pcm_pointer,
};
#endif /* ECHOCARD_HAS_DIGITAL_IO */



/* Preallocate memory only for the first substream because it's the most
 * used one
 */
static void snd_echo_preallocate_pages(struct snd_pcm *pcm, struct device *dev)
{}



/*<--snd_echo_probe() */
static int snd_echo_new_pcm(struct echoaudio *chip)
{}




/******************************************************************************
	Control interface
******************************************************************************/

#if !defined(ECHOCARD_HAS_VMIXER) || defined(ECHOCARD_HAS_LINE_OUT_GAIN)

/******************* PCM output volume *******************/
static int snd_echo_output_gain_info(struct snd_kcontrol *kcontrol,
				     struct snd_ctl_elem_info *uinfo)
{}

static int snd_echo_output_gain_get(struct snd_kcontrol *kcontrol,
				    struct snd_ctl_elem_value *ucontrol)
{}

static int snd_echo_output_gain_put(struct snd_kcontrol *kcontrol,
				    struct snd_ctl_elem_value *ucontrol)
{}

#ifdef ECHOCARD_HAS_LINE_OUT_GAIN
/* On the Mia this one controls the line-out volume */
static const struct snd_kcontrol_new snd_echo_line_output_gain = {
	.name = "Line Playback Volume",
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
		  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
	.info = snd_echo_output_gain_info,
	.get = snd_echo_output_gain_get,
	.put = snd_echo_output_gain_put,
	.tlv = {.p = db_scale_output_gain},
};
#else
static const struct snd_kcontrol_new snd_echo_pcm_output_gain =;
#endif

#endif /* !ECHOCARD_HAS_VMIXER || ECHOCARD_HAS_LINE_OUT_GAIN */



#ifdef ECHOCARD_HAS_INPUT_GAIN

/******************* Analog input volume *******************/
static int snd_echo_input_gain_info(struct snd_kcontrol *kcontrol,
				    struct snd_ctl_elem_info *uinfo)
{
	struct echoaudio *chip;

	chip = snd_kcontrol_chip(kcontrol);
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = num_analog_busses_in(chip);
	uinfo->value.integer.min = ECHOGAIN_MININP;
	uinfo->value.integer.max = ECHOGAIN_MAXINP;
	return 0;
}

static int snd_echo_input_gain_get(struct snd_kcontrol *kcontrol,
				   struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip;
	int c;

	chip = snd_kcontrol_chip(kcontrol);
	for (c = 0; c < num_analog_busses_in(chip); c++)
		ucontrol->value.integer.value[c] = chip->input_gain[c];
	return 0;
}

static int snd_echo_input_gain_put(struct snd_kcontrol *kcontrol,
				   struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip;
	int c, gain, changed;

	changed = 0;
	chip = snd_kcontrol_chip(kcontrol);
	spin_lock_irq(&chip->lock);
	for (c = 0; c < num_analog_busses_in(chip); c++) {
		gain = ucontrol->value.integer.value[c];
		/* Ignore out of range values */
		if (gain < ECHOGAIN_MININP || gain > ECHOGAIN_MAXINP)
			continue;
		if (chip->input_gain[c] != gain) {
			set_input_gain(chip, c, gain);
			changed = 1;
		}
	}
	if (changed)
		update_input_line_level(chip);
	spin_unlock_irq(&chip->lock);
	return changed;
}

static const DECLARE_TLV_DB_SCALE(db_scale_input_gain, -2500, 50, 0);

static const struct snd_kcontrol_new snd_echo_line_input_gain = {
	.name = "Line Capture Volume",
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
	.info = snd_echo_input_gain_info,
	.get = snd_echo_input_gain_get,
	.put = snd_echo_input_gain_put,
	.tlv = {.p = db_scale_input_gain},
};

#endif /* ECHOCARD_HAS_INPUT_GAIN */



#ifdef ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL

/************ Analog output nominal level (+4dBu / -10dBV) ***************/
static int snd_echo_output_nominal_info (struct snd_kcontrol *kcontrol,
					 struct snd_ctl_elem_info *uinfo)
{
	struct echoaudio *chip;

	chip = snd_kcontrol_chip(kcontrol);
	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	uinfo->count = num_analog_busses_out(chip);
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 1;
	return 0;
}

static int snd_echo_output_nominal_get(struct snd_kcontrol *kcontrol,
				       struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip;
	int c;

	chip = snd_kcontrol_chip(kcontrol);
	for (c = 0; c < num_analog_busses_out(chip); c++)
		ucontrol->value.integer.value[c] = chip->nominal_level[c];
	return 0;
}

static int snd_echo_output_nominal_put(struct snd_kcontrol *kcontrol,
				       struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip;
	int c, changed;

	changed = 0;
	chip = snd_kcontrol_chip(kcontrol);
	spin_lock_irq(&chip->lock);
	for (c = 0; c < num_analog_busses_out(chip); c++) {
		if (chip->nominal_level[c] != ucontrol->value.integer.value[c]) {
			set_nominal_level(chip, c,
					  ucontrol->value.integer.value[c]);
			changed = 1;
		}
	}
	if (changed)
		update_output_line_level(chip);
	spin_unlock_irq(&chip->lock);
	return changed;
}

static const struct snd_kcontrol_new snd_echo_output_nominal_level = {
	.name = "Line Playback Switch (-10dBV)",
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.info = snd_echo_output_nominal_info,
	.get = snd_echo_output_nominal_get,
	.put = snd_echo_output_nominal_put,
};

#endif /* ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL */



#ifdef ECHOCARD_HAS_INPUT_NOMINAL_LEVEL

/*************** Analog input nominal level (+4dBu / -10dBV) ***************/
static int snd_echo_input_nominal_info(struct snd_kcontrol *kcontrol,
				       struct snd_ctl_elem_info *uinfo)
{
	struct echoaudio *chip;

	chip = snd_kcontrol_chip(kcontrol);
	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	uinfo->count = num_analog_busses_in(chip);
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 1;
	return 0;
}

static int snd_echo_input_nominal_get(struct snd_kcontrol *kcontrol,
				      struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip;
	int c;

	chip = snd_kcontrol_chip(kcontrol);
	for (c = 0; c < num_analog_busses_in(chip); c++)
		ucontrol->value.integer.value[c] =
			chip->nominal_level[bx_analog_in(chip) + c];
	return 0;
}

static int snd_echo_input_nominal_put(struct snd_kcontrol *kcontrol,
				      struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip;
	int c, changed;

	changed = 0;
	chip = snd_kcontrol_chip(kcontrol);
	spin_lock_irq(&chip->lock);
	for (c = 0; c < num_analog_busses_in(chip); c++) {
		if (chip->nominal_level[bx_analog_in(chip) + c] !=
		    ucontrol->value.integer.value[c]) {
			set_nominal_level(chip, bx_analog_in(chip) + c,
					  ucontrol->value.integer.value[c]);
			changed = 1;
		}
	}
	if (changed)
		update_output_line_level(chip);	/* "Output" is not a mistake
						 * here.
						 */
	spin_unlock_irq(&chip->lock);
	return changed;
}

static const struct snd_kcontrol_new snd_echo_intput_nominal_level = {
	.name = "Line Capture Switch (-10dBV)",
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.info = snd_echo_input_nominal_info,
	.get = snd_echo_input_nominal_get,
	.put = snd_echo_input_nominal_put,
};

#endif /* ECHOCARD_HAS_INPUT_NOMINAL_LEVEL */



#ifdef ECHOCARD_HAS_MONITOR

/******************* Monitor mixer *******************/
static int snd_echo_mixer_info(struct snd_kcontrol *kcontrol,
			       struct snd_ctl_elem_info *uinfo)
{}

static int snd_echo_mixer_get(struct snd_kcontrol *kcontrol,
			      struct snd_ctl_elem_value *ucontrol)
{}

static int snd_echo_mixer_put(struct snd_kcontrol *kcontrol,
			      struct snd_ctl_elem_value *ucontrol)
{}

static struct snd_kcontrol_new snd_echo_monitor_mixer =;

#endif /* ECHOCARD_HAS_MONITOR */



#ifdef ECHOCARD_HAS_VMIXER

/******************* Vmixer *******************/
static int snd_echo_vmixer_info(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 1;
	uinfo->value.integer.min = ECHOGAIN_MINOUT;
	uinfo->value.integer.max = ECHOGAIN_MAXOUT;
	return 0;
}

static int snd_echo_vmixer_get(struct snd_kcontrol *kcontrol,
			       struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip;

	chip = snd_kcontrol_chip(kcontrol);
	ucontrol->value.integer.value[0] =
		chip->vmixer_gain[ucontrol->id.index / num_pipes_out(chip)]
			[ucontrol->id.index % num_pipes_out(chip)];
	return 0;
}

static int snd_echo_vmixer_put(struct snd_kcontrol *kcontrol,
			       struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip;
	int gain, changed;
	short vch, out;

	changed = 0;
	chip = snd_kcontrol_chip(kcontrol);
	out = ucontrol->id.index / num_pipes_out(chip);
	vch = ucontrol->id.index % num_pipes_out(chip);
	gain = ucontrol->value.integer.value[0];
	if (gain < ECHOGAIN_MINOUT || gain > ECHOGAIN_MAXOUT)
		return -EINVAL;
	if (chip->vmixer_gain[out][vch] != ucontrol->value.integer.value[0]) {
		spin_lock_irq(&chip->lock);
		set_vmixer_gain(chip, out, vch, ucontrol->value.integer.value[0]);
		update_vmixer_level(chip);
		spin_unlock_irq(&chip->lock);
		changed = 1;
	}
	return changed;
}

static struct snd_kcontrol_new snd_echo_vmixer = {
	.name = "VMixer Volume",
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
	.info = snd_echo_vmixer_info,
	.get = snd_echo_vmixer_get,
	.put = snd_echo_vmixer_put,
	.tlv = {.p = db_scale_output_gain},
};

#endif /* ECHOCARD_HAS_VMIXER */



#ifdef ECHOCARD_HAS_DIGITAL_MODE_SWITCH

/******************* Digital mode switch *******************/
static int snd_echo_digital_mode_info(struct snd_kcontrol *kcontrol,
				      struct snd_ctl_elem_info *uinfo)
{
	static const char * const names[4] = {
		"S/PDIF Coaxial", "S/PDIF Optical", "ADAT Optical",
		"S/PDIF Cdrom"
	};
	struct echoaudio *chip;

	chip = snd_kcontrol_chip(kcontrol);
	return snd_ctl_enum_info(uinfo, 1, chip->num_digital_modes, names);
}

static int snd_echo_digital_mode_get(struct snd_kcontrol *kcontrol,
				     struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip;
	int i, mode;

	chip = snd_kcontrol_chip(kcontrol);
	mode = chip->digital_mode;
	for (i = chip->num_digital_modes - 1; i >= 0; i--)
		if (mode == chip->digital_mode_list[i]) {
			ucontrol->value.enumerated.item[0] = i;
			break;
		}
	return 0;
}

static int snd_echo_digital_mode_put(struct snd_kcontrol *kcontrol,
				     struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip;
	int changed;
	unsigned short emode, dmode;

	changed = 0;
	chip = snd_kcontrol_chip(kcontrol);

	emode = ucontrol->value.enumerated.item[0];
	if (emode >= chip->num_digital_modes)
		return -EINVAL;
	dmode = chip->digital_mode_list[emode];

	if (dmode != chip->digital_mode) {
		/* mode_mutex is required to make this operation atomic wrt
		pcm_digital_*_open() and set_input_clock() functions. */
		mutex_lock(&chip->mode_mutex);

		/* Do not allow the user to change the digital mode when a pcm
		device is open because it also changes the number of channels
		and the allowed sample rates */
		if (chip->opencount) {
			changed = -EAGAIN;
		} else {
			changed = set_digital_mode(chip, dmode);
			/* If we had to change the clock source, report it */
			if (changed > 0 && chip->clock_src_ctl) {
				snd_ctl_notify(chip->card,
					       SNDRV_CTL_EVENT_MASK_VALUE,
					       &chip->clock_src_ctl->id);
				dev_dbg(chip->card->dev,
					"SDM() =%d\n", changed);
			}
			if (changed >= 0)
				changed = 1;	/* No errors */
		}
		mutex_unlock(&chip->mode_mutex);
	}
	return changed;
}

static const struct snd_kcontrol_new snd_echo_digital_mode_switch = {
	.name = "Digital mode Switch",
	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
	.info = snd_echo_digital_mode_info,
	.get = snd_echo_digital_mode_get,
	.put = snd_echo_digital_mode_put,
};

#endif /* ECHOCARD_HAS_DIGITAL_MODE_SWITCH */



#ifdef ECHOCARD_HAS_DIGITAL_IO

/******************* S/PDIF mode switch *******************/
static int snd_echo_spdif_mode_info(struct snd_kcontrol *kcontrol,
				    struct snd_ctl_elem_info *uinfo)
{
	static const char * const names[2] = {"Consumer", "Professional"};

	return snd_ctl_enum_info(uinfo, 1, 2, names);
}

static int snd_echo_spdif_mode_get(struct snd_kcontrol *kcontrol,
				   struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip;

	chip = snd_kcontrol_chip(kcontrol);
	ucontrol->value.enumerated.item[0] = !!chip->professional_spdif;
	return 0;
}

static int snd_echo_spdif_mode_put(struct snd_kcontrol *kcontrol,
				   struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip;
	int mode;

	chip = snd_kcontrol_chip(kcontrol);
	mode = !!ucontrol->value.enumerated.item[0];
	if (mode != chip->professional_spdif) {
		spin_lock_irq(&chip->lock);
		set_professional_spdif(chip, mode);
		spin_unlock_irq(&chip->lock);
		return 1;
	}
	return 0;
}

static const struct snd_kcontrol_new snd_echo_spdif_mode_switch = {
	.name = "S/PDIF mode Switch",
	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
	.info = snd_echo_spdif_mode_info,
	.get = snd_echo_spdif_mode_get,
	.put = snd_echo_spdif_mode_put,
};

#endif /* ECHOCARD_HAS_DIGITAL_IO */



#ifdef ECHOCARD_HAS_EXTERNAL_CLOCK

/******************* Select input clock source *******************/
static int snd_echo_clock_source_info(struct snd_kcontrol *kcontrol,
				      struct snd_ctl_elem_info *uinfo)
{
	static const char * const names[8] = {
		"Internal", "Word", "Super", "S/PDIF", "ADAT", "ESync",
		"ESync96", "MTC"
	};
	struct echoaudio *chip;

	chip = snd_kcontrol_chip(kcontrol);
	return snd_ctl_enum_info(uinfo, 1, chip->num_clock_sources, names);
}

static int snd_echo_clock_source_get(struct snd_kcontrol *kcontrol,
				     struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip;
	int i, clock;

	chip = snd_kcontrol_chip(kcontrol);
	clock = chip->input_clock;

	for (i = 0; i < chip->num_clock_sources; i++)
		if (clock == chip->clock_source_list[i])
			ucontrol->value.enumerated.item[0] = i;

	return 0;
}

static int snd_echo_clock_source_put(struct snd_kcontrol *kcontrol,
				     struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip;
	int changed;
	unsigned int eclock, dclock;

	changed = 0;
	chip = snd_kcontrol_chip(kcontrol);
	eclock = ucontrol->value.enumerated.item[0];
	if (eclock >= chip->input_clock_types)
		return -EINVAL;
	dclock = chip->clock_source_list[eclock];
	if (chip->input_clock != dclock) {
		mutex_lock(&chip->mode_mutex);
		spin_lock_irq(&chip->lock);
		changed = set_input_clock(chip, dclock);
		if (!changed)
			changed = 1;	/* no errors */
		spin_unlock_irq(&chip->lock);
		mutex_unlock(&chip->mode_mutex);
	}

	if (changed < 0)
		dev_dbg(chip->card->dev,
			"seticlk val%d err 0x%x\n", dclock, changed);

	return changed;
}

static const struct snd_kcontrol_new snd_echo_clock_source_switch = {
	.name = "Sample Clock Source",
	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
	.info = snd_echo_clock_source_info,
	.get = snd_echo_clock_source_get,
	.put = snd_echo_clock_source_put,
};

#endif /* ECHOCARD_HAS_EXTERNAL_CLOCK */



#ifdef ECHOCARD_HAS_PHANTOM_POWER

/******************* Phantom power switch *******************/
#define snd_echo_phantom_power_info

static int snd_echo_phantom_power_get(struct snd_kcontrol *kcontrol,
				      struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip = snd_kcontrol_chip(kcontrol);

	ucontrol->value.integer.value[0] = chip->phantom_power;
	return 0;
}

static int snd_echo_phantom_power_put(struct snd_kcontrol *kcontrol,
				      struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip = snd_kcontrol_chip(kcontrol);
	int power, changed = 0;

	power = !!ucontrol->value.integer.value[0];
	if (chip->phantom_power != power) {
		spin_lock_irq(&chip->lock);
		changed = set_phantom_power(chip, power);
		spin_unlock_irq(&chip->lock);
		if (changed == 0)
			changed = 1;	/* no errors */
	}
	return changed;
}

static const struct snd_kcontrol_new snd_echo_phantom_power_switch = {
	.name = "Phantom power Switch",
	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
	.info = snd_echo_phantom_power_info,
	.get = snd_echo_phantom_power_get,
	.put = snd_echo_phantom_power_put,
};

#endif /* ECHOCARD_HAS_PHANTOM_POWER */



#ifdef ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE

/******************* Digital input automute switch *******************/
#define snd_echo_automute_info

static int snd_echo_automute_get(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip = snd_kcontrol_chip(kcontrol);

	ucontrol->value.integer.value[0] = chip->digital_in_automute;
	return 0;
}

static int snd_echo_automute_put(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	struct echoaudio *chip = snd_kcontrol_chip(kcontrol);
	int automute, changed = 0;

	automute = !!ucontrol->value.integer.value[0];
	if (chip->digital_in_automute != automute) {
		spin_lock_irq(&chip->lock);
		changed = set_input_auto_mute(chip, automute);
		spin_unlock_irq(&chip->lock);
		if (changed == 0)
			changed = 1;	/* no errors */
	}
	return changed;
}

static const struct snd_kcontrol_new snd_echo_automute_switch = {
	.name = "Digital Capture Switch (automute)",
	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
	.info = snd_echo_automute_info,
	.get = snd_echo_automute_get,
	.put = snd_echo_automute_put,
};

#endif /* ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE */



/******************* VU-meters switch *******************/
#define snd_echo_vumeters_switch_info

static int snd_echo_vumeters_switch_put(struct snd_kcontrol *kcontrol,
					struct snd_ctl_elem_value *ucontrol)
{}

static const struct snd_kcontrol_new snd_echo_vumeters_switch =;



/***** Read VU-meters (input, output, analog and digital together) *****/
static int snd_echo_vumeters_info(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_info *uinfo)
{}

static int snd_echo_vumeters_get(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{}

static const struct snd_kcontrol_new snd_echo_vumeters =;



/*** Channels info - it exports informations about the number of channels ***/
static int snd_echo_channels_info_info(struct snd_kcontrol *kcontrol,
				       struct snd_ctl_elem_info *uinfo)
{}

static int snd_echo_channels_info_get(struct snd_kcontrol *kcontrol,
				      struct snd_ctl_elem_value *ucontrol)
{}

static const struct snd_kcontrol_new snd_echo_channels_info =;




/******************************************************************************
	IRQ Handling
******************************************************************************/
/* Check if a period has elapsed since last interrupt
 *
 * Don't make any updates to state; PCM core handles this with the
 * correct locks.
 *
 * \return true if a period has elapsed, otherwise false
 */
static bool period_has_elapsed(struct snd_pcm_substream *substream)
{}

static irqreturn_t snd_echo_interrupt(int irq, void *dev_id)
{}




/******************************************************************************
	Module construction / destruction
******************************************************************************/

static void snd_echo_free(struct snd_card *card)
{}

/* <--snd_echo_probe() */
static int snd_echo_create(struct snd_card *card,
			   struct pci_dev *pci)
{}

/* constructor */
static int __snd_echo_probe(struct pci_dev *pci,
			    const struct pci_device_id *pci_id)
{}

static int snd_echo_probe(struct pci_dev *pci,
			  const struct pci_device_id *pci_id)
{}


static int snd_echo_suspend(struct device *dev)
{}



static int snd_echo_resume(struct device *dev)
{}

static DEFINE_SIMPLE_DEV_PM_OPS(snd_echo_pm, snd_echo_suspend, snd_echo_resume);

/******************************************************************************
	Everything starts and ends here
******************************************************************************/

/* pci_driver definition */
static struct pci_driver echo_driver =;

module_pci_driver();