diff options
Diffstat (limited to 'sound/pci/hda/hda_generic.c')
-rw-r--r-- | sound/pci/hda/hda_generic.c | 1085 |
1 files changed, 1085 insertions, 0 deletions
diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c new file mode 100644 index 00000000..431bf868 --- /dev/null +++ b/sound/pci/hda/hda_generic.c @@ -0,0 +1,1085 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * Generic widget tree parser + * + * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de> + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/export.h> +#include <sound/core.h> +#include "hda_codec.h" +#include "hda_local.h" + +/* widget node for parsing */ +struct hda_gnode { + hda_nid_t nid; /* NID of this widget */ + unsigned short nconns; /* number of input connections */ + hda_nid_t *conn_list; + hda_nid_t slist[2]; /* temporay list */ + unsigned int wid_caps; /* widget capabilities */ + unsigned char type; /* widget type */ + unsigned char pin_ctl; /* pin controls */ + unsigned char checked; /* the flag indicates that the node is already parsed */ + unsigned int pin_caps; /* pin widget capabilities */ + unsigned int def_cfg; /* default configuration */ + unsigned int amp_out_caps; /* AMP out capabilities */ + unsigned int amp_in_caps; /* AMP in capabilities */ + struct list_head list; +}; + +/* patch-specific record */ + +#define MAX_PCM_VOLS 2 +struct pcm_vol { + struct hda_gnode *node; /* Node for PCM volume */ + unsigned int index; /* connection of PCM volume */ +}; + +struct hda_gspec { + struct hda_gnode *dac_node[2]; /* DAC node */ + struct hda_gnode *out_pin_node[2]; /* Output pin (Line-Out) node */ + struct pcm_vol pcm_vol[MAX_PCM_VOLS]; /* PCM volumes */ + unsigned int pcm_vol_nodes; /* number of PCM volumes */ + + struct hda_gnode *adc_node; /* ADC node */ + struct hda_gnode *cap_vol_node; /* Node for capture volume */ + unsigned int cur_cap_src; /* current capture source */ + struct hda_input_mux input_mux; + + unsigned int def_amp_in_caps; + unsigned int def_amp_out_caps; + + struct hda_pcm pcm_rec; /* PCM information */ + + struct list_head nid_list; /* list of widgets */ + +#ifdef CONFIG_SND_HDA_POWER_SAVE +#define MAX_LOOPBACK_AMPS 7 + struct hda_loopback_check loopback; + int num_loopbacks; + struct hda_amp_list loopback_list[MAX_LOOPBACK_AMPS + 1]; +#endif +}; + +/* + * retrieve the default device type from the default config value + */ +#define defcfg_type(node) (((node)->def_cfg & AC_DEFCFG_DEVICE) >> \ + AC_DEFCFG_DEVICE_SHIFT) +#define defcfg_location(node) (((node)->def_cfg & AC_DEFCFG_LOCATION) >> \ + AC_DEFCFG_LOCATION_SHIFT) +#define defcfg_port_conn(node) (((node)->def_cfg & AC_DEFCFG_PORT_CONN) >> \ + AC_DEFCFG_PORT_CONN_SHIFT) + +/* + * destructor + */ +static void snd_hda_generic_free(struct hda_codec *codec) +{ + struct hda_gspec *spec = codec->spec; + struct hda_gnode *node, *n; + + if (! spec) + return; + /* free all widgets */ + list_for_each_entry_safe(node, n, &spec->nid_list, list) { + if (node->conn_list != node->slist) + kfree(node->conn_list); + kfree(node); + } + kfree(spec); +} + + +/* + * add a new widget node and read its attributes + */ +static int add_new_node(struct hda_codec *codec, struct hda_gspec *spec, hda_nid_t nid) +{ + struct hda_gnode *node; + int nconns; + hda_nid_t conn_list[HDA_MAX_CONNECTIONS]; + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (node == NULL) + return -ENOMEM; + node->nid = nid; + node->wid_caps = get_wcaps(codec, nid); + node->type = get_wcaps_type(node->wid_caps); + if (node->wid_caps & AC_WCAP_CONN_LIST) { + nconns = snd_hda_get_connections(codec, nid, conn_list, + HDA_MAX_CONNECTIONS); + if (nconns < 0) { + kfree(node); + return nconns; + } + } else { + nconns = 0; + } + if (nconns <= ARRAY_SIZE(node->slist)) + node->conn_list = node->slist; + else { + node->conn_list = kmalloc(sizeof(hda_nid_t) * nconns, + GFP_KERNEL); + if (! node->conn_list) { + snd_printk(KERN_ERR "hda-generic: cannot malloc\n"); + kfree(node); + return -ENOMEM; + } + } + memcpy(node->conn_list, conn_list, nconns * sizeof(hda_nid_t)); + node->nconns = nconns; + + if (node->type == AC_WID_PIN) { + node->pin_caps = snd_hda_query_pin_caps(codec, node->nid); + node->pin_ctl = snd_hda_codec_read(codec, node->nid, 0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + node->def_cfg = snd_hda_codec_get_pincfg(codec, node->nid); + } + + if (node->wid_caps & AC_WCAP_OUT_AMP) { + if (node->wid_caps & AC_WCAP_AMP_OVRD) + node->amp_out_caps = snd_hda_param_read(codec, node->nid, AC_PAR_AMP_OUT_CAP); + if (! node->amp_out_caps) + node->amp_out_caps = spec->def_amp_out_caps; + } + if (node->wid_caps & AC_WCAP_IN_AMP) { + if (node->wid_caps & AC_WCAP_AMP_OVRD) + node->amp_in_caps = snd_hda_param_read(codec, node->nid, AC_PAR_AMP_IN_CAP); + if (! node->amp_in_caps) + node->amp_in_caps = spec->def_amp_in_caps; + } + list_add_tail(&node->list, &spec->nid_list); + return 0; +} + +/* + * build the AFG subtree + */ +static int build_afg_tree(struct hda_codec *codec) +{ + struct hda_gspec *spec = codec->spec; + int i, nodes, err; + hda_nid_t nid; + + if (snd_BUG_ON(!spec)) + return -EINVAL; + + spec->def_amp_out_caps = snd_hda_param_read(codec, codec->afg, AC_PAR_AMP_OUT_CAP); + spec->def_amp_in_caps = snd_hda_param_read(codec, codec->afg, AC_PAR_AMP_IN_CAP); + + nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid); + if (! nid || nodes < 0) { + printk(KERN_ERR "Invalid AFG subtree\n"); + return -EINVAL; + } + + /* parse all nodes belonging to the AFG */ + for (i = 0; i < nodes; i++, nid++) { + if ((err = add_new_node(codec, spec, nid)) < 0) + return err; + } + + return 0; +} + + +/* + * look for the node record for the given NID + */ +/* FIXME: should avoid the braindead linear search */ +static struct hda_gnode *hda_get_node(struct hda_gspec *spec, hda_nid_t nid) +{ + struct hda_gnode *node; + + list_for_each_entry(node, &spec->nid_list, list) { + if (node->nid == nid) + return node; + } + return NULL; +} + +/* + * unmute (and set max vol) the output amplifier + */ +static int unmute_output(struct hda_codec *codec, struct hda_gnode *node) +{ + unsigned int val, ofs; + snd_printdd("UNMUTE OUT: NID=0x%x\n", node->nid); + val = (node->amp_out_caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT; + ofs = (node->amp_out_caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT; + if (val >= ofs) + val -= ofs; + snd_hda_codec_amp_stereo(codec, node->nid, HDA_OUTPUT, 0, 0xff, val); + return 0; +} + +/* + * unmute (and set max vol) the input amplifier + */ +static int unmute_input(struct hda_codec *codec, struct hda_gnode *node, unsigned int index) +{ + unsigned int val, ofs; + snd_printdd("UNMUTE IN: NID=0x%x IDX=0x%x\n", node->nid, index); + val = (node->amp_in_caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT; + ofs = (node->amp_in_caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT; + if (val >= ofs) + val -= ofs; + snd_hda_codec_amp_stereo(codec, node->nid, HDA_INPUT, index, 0xff, val); + return 0; +} + +/* + * select the input connection of the given node. + */ +static int select_input_connection(struct hda_codec *codec, struct hda_gnode *node, + unsigned int index) +{ + snd_printdd("CONNECT: NID=0x%x IDX=0x%x\n", node->nid, index); + return snd_hda_codec_write_cache(codec, node->nid, 0, + AC_VERB_SET_CONNECT_SEL, index); +} + +/* + * clear checked flag of each node in the node list + */ +static void clear_check_flags(struct hda_gspec *spec) +{ + struct hda_gnode *node; + + list_for_each_entry(node, &spec->nid_list, list) { + node->checked = 0; + } +} + +/* + * parse the output path recursively until reach to an audio output widget + * + * returns 0 if not found, 1 if found, or a negative error code. + */ +static int parse_output_path(struct hda_codec *codec, struct hda_gspec *spec, + struct hda_gnode *node, int dac_idx) +{ + int i, err; + struct hda_gnode *child; + + if (node->checked) + return 0; + + node->checked = 1; + if (node->type == AC_WID_AUD_OUT) { + if (node->wid_caps & AC_WCAP_DIGITAL) { + snd_printdd("Skip Digital OUT node %x\n", node->nid); + return 0; + } + snd_printdd("AUD_OUT found %x\n", node->nid); + if (spec->dac_node[dac_idx]) { + /* already DAC node is assigned, just unmute & connect */ + return node == spec->dac_node[dac_idx]; + } + spec->dac_node[dac_idx] = node; + if ((node->wid_caps & AC_WCAP_OUT_AMP) && + spec->pcm_vol_nodes < MAX_PCM_VOLS) { + spec->pcm_vol[spec->pcm_vol_nodes].node = node; + spec->pcm_vol[spec->pcm_vol_nodes].index = 0; + spec->pcm_vol_nodes++; + } + return 1; /* found */ + } + + for (i = 0; i < node->nconns; i++) { + child = hda_get_node(spec, node->conn_list[i]); + if (! child) + continue; + err = parse_output_path(codec, spec, child, dac_idx); + if (err < 0) + return err; + else if (err > 0) { + /* found one, + * select the path, unmute both input and output + */ + if (node->nconns > 1) + select_input_connection(codec, node, i); + unmute_input(codec, node, i); + unmute_output(codec, node); + if (spec->dac_node[dac_idx] && + spec->pcm_vol_nodes < MAX_PCM_VOLS && + !(spec->dac_node[dac_idx]->wid_caps & + AC_WCAP_OUT_AMP)) { + if ((node->wid_caps & AC_WCAP_IN_AMP) || + (node->wid_caps & AC_WCAP_OUT_AMP)) { + int n = spec->pcm_vol_nodes; + spec->pcm_vol[n].node = node; + spec->pcm_vol[n].index = i; + spec->pcm_vol_nodes++; + } + } + return 1; + } + } + return 0; +} + +/* + * Look for the output PIN widget with the given jack type + * and parse the output path to that PIN. + * + * Returns the PIN node when the path to DAC is established. + */ +static struct hda_gnode *parse_output_jack(struct hda_codec *codec, + struct hda_gspec *spec, + int jack_type) +{ + struct hda_gnode *node; + int err; + + list_for_each_entry(node, &spec->nid_list, list) { + if (node->type != AC_WID_PIN) + continue; + /* output capable? */ + if (! (node->pin_caps & AC_PINCAP_OUT)) + continue; + if (defcfg_port_conn(node) == AC_JACK_PORT_NONE) + continue; /* unconnected */ + if (jack_type >= 0) { + if (jack_type != defcfg_type(node)) + continue; + if (node->wid_caps & AC_WCAP_DIGITAL) + continue; /* skip SPDIF */ + } else { + /* output as default? */ + if (! (node->pin_ctl & AC_PINCTL_OUT_EN)) + continue; + } + clear_check_flags(spec); + err = parse_output_path(codec, spec, node, 0); + if (err < 0) + return NULL; + if (! err && spec->out_pin_node[0]) { + err = parse_output_path(codec, spec, node, 1); + if (err < 0) + return NULL; + } + if (err > 0) { + /* unmute the PIN output */ + unmute_output(codec, node); + /* set PIN-Out enable */ + snd_hda_codec_write_cache(codec, node->nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + AC_PINCTL_OUT_EN | + ((node->pin_caps & AC_PINCAP_HP_DRV) ? + AC_PINCTL_HP_EN : 0)); + return node; + } + } + return NULL; +} + + +/* + * parse outputs + */ +static int parse_output(struct hda_codec *codec) +{ + struct hda_gspec *spec = codec->spec; + struct hda_gnode *node; + + /* + * Look for the output PIN widget + */ + /* first, look for the line-out pin */ + node = parse_output_jack(codec, spec, AC_JACK_LINE_OUT); + if (node) /* found, remember the PIN node */ + spec->out_pin_node[0] = node; + else { + /* if no line-out is found, try speaker out */ + node = parse_output_jack(codec, spec, AC_JACK_SPEAKER); + if (node) + spec->out_pin_node[0] = node; + } + /* look for the HP-out pin */ + node = parse_output_jack(codec, spec, AC_JACK_HP_OUT); + if (node) { + if (! spec->out_pin_node[0]) + spec->out_pin_node[0] = node; + else + spec->out_pin_node[1] = node; + } + + if (! spec->out_pin_node[0]) { + /* no line-out or HP pins found, + * then choose for the first output pin + */ + spec->out_pin_node[0] = parse_output_jack(codec, spec, -1); + if (! spec->out_pin_node[0]) + snd_printd("hda_generic: no proper output path found\n"); + } + + return 0; +} + +/* + * input MUX + */ + +/* control callbacks */ +static int capture_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gspec *spec = codec->spec; + return snd_hda_input_mux_info(&spec->input_mux, uinfo); +} + +static int capture_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gspec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->cur_cap_src; + return 0; +} + +static int capture_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gspec *spec = codec->spec; + return snd_hda_input_mux_put(codec, &spec->input_mux, ucontrol, + spec->adc_node->nid, &spec->cur_cap_src); +} + +/* + * return the string name of the given input PIN widget + */ +static const char *get_input_type(struct hda_gnode *node, unsigned int *pinctl) +{ + unsigned int location = defcfg_location(node); + switch (defcfg_type(node)) { + case AC_JACK_LINE_IN: + if ((location & 0x0f) == AC_JACK_LOC_FRONT) + return "Front Line"; + return "Line"; + case AC_JACK_CD: +#if 0 + if (pinctl) + *pinctl |= AC_PINCTL_VREF_GRD; +#endif + return "CD"; + case AC_JACK_AUX: + if ((location & 0x0f) == AC_JACK_LOC_FRONT) + return "Front Aux"; + return "Aux"; + case AC_JACK_MIC_IN: + if (pinctl && + (node->pin_caps & + (AC_PINCAP_VREF_80 << AC_PINCAP_VREF_SHIFT))) + *pinctl |= AC_PINCTL_VREF_80; + if ((location & 0x0f) == AC_JACK_LOC_FRONT) + return "Front Mic"; + return "Mic"; + case AC_JACK_SPDIF_IN: + return "SPDIF"; + case AC_JACK_DIG_OTHER_IN: + return "Digital"; + } + return NULL; +} + +/* + * parse the nodes recursively until reach to the input PIN + * + * returns 0 if not found, 1 if found, or a negative error code. + */ +static int parse_adc_sub_nodes(struct hda_codec *codec, struct hda_gspec *spec, + struct hda_gnode *node, int idx) +{ + int i, err; + unsigned int pinctl; + const char *type; + + if (node->checked) + return 0; + + node->checked = 1; + if (node->type != AC_WID_PIN) { + for (i = 0; i < node->nconns; i++) { + struct hda_gnode *child; + child = hda_get_node(spec, node->conn_list[i]); + if (! child) + continue; + err = parse_adc_sub_nodes(codec, spec, child, idx); + if (err < 0) + return err; + if (err > 0) { + /* found one, + * select the path, unmute both input and output + */ + if (node->nconns > 1) + select_input_connection(codec, node, i); + unmute_input(codec, node, i); + unmute_output(codec, node); + return err; + } + } + return 0; + } + + /* input capable? */ + if (! (node->pin_caps & AC_PINCAP_IN)) + return 0; + + if (defcfg_port_conn(node) == AC_JACK_PORT_NONE) + return 0; /* unconnected */ + + if (node->wid_caps & AC_WCAP_DIGITAL) + return 0; /* skip SPDIF */ + + if (spec->input_mux.num_items >= HDA_MAX_NUM_INPUTS) { + snd_printk(KERN_ERR "hda_generic: Too many items for capture\n"); + return -EINVAL; + } + + pinctl = AC_PINCTL_IN_EN; + /* create a proper capture source label */ + type = get_input_type(node, &pinctl); + if (! type) { + /* input as default? */ + if (! (node->pin_ctl & AC_PINCTL_IN_EN)) + return 0; + type = "Input"; + } + snd_hda_add_imux_item(&spec->input_mux, type, idx, NULL); + + /* unmute the PIN external input */ + unmute_input(codec, node, 0); /* index = 0? */ + /* set PIN-In enable */ + snd_hda_codec_write_cache(codec, node->nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, pinctl); + + return 1; /* found */ +} + +/* + * parse input + */ +static int parse_input_path(struct hda_codec *codec, struct hda_gnode *adc_node) +{ + struct hda_gspec *spec = codec->spec; + struct hda_gnode *node; + int i, err; + + snd_printdd("AUD_IN = %x\n", adc_node->nid); + clear_check_flags(spec); + + // awk added - fixed no recording due to muted widget + unmute_input(codec, adc_node, 0); + + /* + * check each connection of the ADC + * if it reaches to a proper input PIN, add the path as the + * input path. + */ + /* first, check the direct connections to PIN widgets */ + for (i = 0; i < adc_node->nconns; i++) { + node = hda_get_node(spec, adc_node->conn_list[i]); + if (node && node->type == AC_WID_PIN) { + err = parse_adc_sub_nodes(codec, spec, node, i); + if (err < 0) + return err; + } + } + /* ... then check the rests, more complicated connections */ + for (i = 0; i < adc_node->nconns; i++) { + node = hda_get_node(spec, adc_node->conn_list[i]); + if (node && node->type != AC_WID_PIN) { + err = parse_adc_sub_nodes(codec, spec, node, i); + if (err < 0) + return err; + } + } + + if (! spec->input_mux.num_items) + return 0; /* no input path found... */ + + snd_printdd("[Capture Source] NID=0x%x, #SRC=%d\n", adc_node->nid, spec->input_mux.num_items); + for (i = 0; i < spec->input_mux.num_items; i++) + snd_printdd(" [%s] IDX=0x%x\n", spec->input_mux.items[i].label, + spec->input_mux.items[i].index); + + spec->adc_node = adc_node; + return 1; +} + +/* + * parse input + */ +static int parse_input(struct hda_codec *codec) +{ + struct hda_gspec *spec = codec->spec; + struct hda_gnode *node; + int err; + + /* + * At first we look for an audio input widget. + * If it reaches to certain input PINs, we take it as the + * input path. + */ + list_for_each_entry(node, &spec->nid_list, list) { + if (node->wid_caps & AC_WCAP_DIGITAL) + continue; /* skip SPDIF */ + if (node->type == AC_WID_AUD_IN) { + err = parse_input_path(codec, node); + if (err < 0) + return err; + else if (err > 0) + return 0; + } + } + snd_printd("hda_generic: no proper input path found\n"); + return 0; +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static void add_input_loopback(struct hda_codec *codec, hda_nid_t nid, + int dir, int idx) +{ + struct hda_gspec *spec = codec->spec; + struct hda_amp_list *p; + + if (spec->num_loopbacks >= MAX_LOOPBACK_AMPS) { + snd_printk(KERN_ERR "hda_generic: Too many loopback ctls\n"); + return; + } + p = &spec->loopback_list[spec->num_loopbacks++]; + p->nid = nid; + p->dir = dir; + p->idx = idx; + spec->loopback.amplist = spec->loopback_list; +} +#else +#define add_input_loopback(codec,nid,dir,idx) +#endif + +/* + * create mixer controls if possible + */ +static int create_mixer(struct hda_codec *codec, struct hda_gnode *node, + unsigned int index, const char *type, + const char *dir_sfx, int is_loopback) +{ + char name[32]; + int err; + int created = 0; + struct snd_kcontrol_new knew; + + if (type) + sprintf(name, "%s %s Switch", type, dir_sfx); + else + sprintf(name, "%s Switch", dir_sfx); + if ((node->wid_caps & AC_WCAP_IN_AMP) && + (node->amp_in_caps & AC_AMPCAP_MUTE)) { + knew = (struct snd_kcontrol_new)HDA_CODEC_MUTE(name, node->nid, index, HDA_INPUT); + if (is_loopback) + add_input_loopback(codec, node->nid, HDA_INPUT, index); + snd_printdd("[%s] NID=0x%x, DIR=IN, IDX=0x%x\n", name, node->nid, index); + err = snd_hda_ctl_add(codec, node->nid, + snd_ctl_new1(&knew, codec)); + if (err < 0) + return err; + created = 1; + } else if ((node->wid_caps & AC_WCAP_OUT_AMP) && + (node->amp_out_caps & AC_AMPCAP_MUTE)) { + knew = (struct snd_kcontrol_new)HDA_CODEC_MUTE(name, node->nid, 0, HDA_OUTPUT); + if (is_loopback) + add_input_loopback(codec, node->nid, HDA_OUTPUT, 0); + snd_printdd("[%s] NID=0x%x, DIR=OUT\n", name, node->nid); + err = snd_hda_ctl_add(codec, node->nid, + snd_ctl_new1(&knew, codec)); + if (err < 0) + return err; + created = 1; + } + + if (type) + sprintf(name, "%s %s Volume", type, dir_sfx); + else + sprintf(name, "%s Volume", dir_sfx); + if ((node->wid_caps & AC_WCAP_IN_AMP) && + (node->amp_in_caps & AC_AMPCAP_NUM_STEPS)) { + knew = (struct snd_kcontrol_new)HDA_CODEC_VOLUME(name, node->nid, index, HDA_INPUT); + snd_printdd("[%s] NID=0x%x, DIR=IN, IDX=0x%x\n", name, node->nid, index); + err = snd_hda_ctl_add(codec, node->nid, + snd_ctl_new1(&knew, codec)); + if (err < 0) + return err; + created = 1; + } else if ((node->wid_caps & AC_WCAP_OUT_AMP) && + (node->amp_out_caps & AC_AMPCAP_NUM_STEPS)) { + knew = (struct snd_kcontrol_new)HDA_CODEC_VOLUME(name, node->nid, 0, HDA_OUTPUT); + snd_printdd("[%s] NID=0x%x, DIR=OUT\n", name, node->nid); + err = snd_hda_ctl_add(codec, node->nid, + snd_ctl_new1(&knew, codec)); + if (err < 0) + return err; + created = 1; + } + + return created; +} + +/* + * check whether the controls with the given name and direction suffix already exist + */ +static int check_existing_control(struct hda_codec *codec, const char *type, const char *dir) +{ + struct snd_ctl_elem_id id; + memset(&id, 0, sizeof(id)); + sprintf(id.name, "%s %s Volume", type, dir); + id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + if (snd_ctl_find_id(codec->bus->card, &id)) + return 1; + sprintf(id.name, "%s %s Switch", type, dir); + id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + if (snd_ctl_find_id(codec->bus->card, &id)) + return 1; + return 0; +} + +/* + * build output mixer controls + */ +static int create_output_mixers(struct hda_codec *codec, + const char * const *names) +{ + struct hda_gspec *spec = codec->spec; + int i, err; + + for (i = 0; i < spec->pcm_vol_nodes; i++) { + err = create_mixer(codec, spec->pcm_vol[i].node, + spec->pcm_vol[i].index, + names[i], "Playback", 0); + if (err < 0) + return err; + } + return 0; +} + +static int build_output_controls(struct hda_codec *codec) +{ + struct hda_gspec *spec = codec->spec; + static const char * const types_speaker[] = { "Speaker", "Headphone" }; + static const char * const types_line[] = { "Front", "Headphone" }; + + switch (spec->pcm_vol_nodes) { + case 1: + return create_mixer(codec, spec->pcm_vol[0].node, + spec->pcm_vol[0].index, + "Master", "Playback", 0); + case 2: + if (defcfg_type(spec->out_pin_node[0]) == AC_JACK_SPEAKER) + return create_output_mixers(codec, types_speaker); + else + return create_output_mixers(codec, types_line); + } + return 0; +} + +/* create capture volume/switch */ +static int build_input_controls(struct hda_codec *codec) +{ + struct hda_gspec *spec = codec->spec; + struct hda_gnode *adc_node = spec->adc_node; + int i, err; + static struct snd_kcontrol_new cap_sel = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = capture_source_info, + .get = capture_source_get, + .put = capture_source_put, + }; + + if (! adc_node || ! spec->input_mux.num_items) + return 0; /* not found */ + + spec->cur_cap_src = 0; + select_input_connection(codec, adc_node, + spec->input_mux.items[0].index); + + /* create capture volume and switch controls if the ADC has an amp */ + /* do we have only a single item? */ + if (spec->input_mux.num_items == 1) { + err = create_mixer(codec, adc_node, + spec->input_mux.items[0].index, + NULL, "Capture", 0); + if (err < 0) + return err; + return 0; + } + + /* create input MUX if multiple sources are available */ + err = snd_hda_ctl_add(codec, spec->adc_node->nid, + snd_ctl_new1(&cap_sel, codec)); + if (err < 0) + return err; + + /* no volume control? */ + if (! (adc_node->wid_caps & AC_WCAP_IN_AMP) || + ! (adc_node->amp_in_caps & AC_AMPCAP_NUM_STEPS)) + return 0; + + for (i = 0; i < spec->input_mux.num_items; i++) { + struct snd_kcontrol_new knew; + char name[32]; + sprintf(name, "%s Capture Volume", + spec->input_mux.items[i].label); + knew = (struct snd_kcontrol_new) + HDA_CODEC_VOLUME(name, adc_node->nid, + spec->input_mux.items[i].index, + HDA_INPUT); + err = snd_hda_ctl_add(codec, adc_node->nid, + snd_ctl_new1(&knew, codec)); + if (err < 0) + return err; + } + + return 0; +} + + +/* + * parse the nodes recursively until reach to the output PIN. + * + * returns 0 - if not found, + * 1 - if found, but no mixer is created + * 2 - if found and mixer was already created, (just skip) + * a negative error code + */ +static int parse_loopback_path(struct hda_codec *codec, struct hda_gspec *spec, + struct hda_gnode *node, struct hda_gnode *dest_node, + const char *type) +{ + int i, err; + + if (node->checked) + return 0; + + node->checked = 1; + if (node == dest_node) { + /* loopback connection found */ + return 1; + } + + for (i = 0; i < node->nconns; i++) { + struct hda_gnode *child = hda_get_node(spec, node->conn_list[i]); + if (! child) + continue; + err = parse_loopback_path(codec, spec, child, dest_node, type); + if (err < 0) + return err; + else if (err >= 1) { + if (err == 1) { + err = create_mixer(codec, node, i, type, + "Playback", 1); + if (err < 0) + return err; + if (err > 0) + return 2; /* ok, created */ + /* not created, maybe in the lower path */ + err = 1; + } + /* connect and unmute */ + if (node->nconns > 1) + select_input_connection(codec, node, i); + unmute_input(codec, node, i); + unmute_output(codec, node); + return err; + } + } + return 0; +} + +/* + * parse the tree and build the loopback controls + */ +static int build_loopback_controls(struct hda_codec *codec) +{ + struct hda_gspec *spec = codec->spec; + struct hda_gnode *node; + int err; + const char *type; + + if (! spec->out_pin_node[0]) + return 0; + + list_for_each_entry(node, &spec->nid_list, list) { + if (node->type != AC_WID_PIN) + continue; + /* input capable? */ + if (! (node->pin_caps & AC_PINCAP_IN)) + return 0; + type = get_input_type(node, NULL); + if (type) { + if (check_existing_control(codec, type, "Playback")) + continue; + clear_check_flags(spec); + err = parse_loopback_path(codec, spec, + spec->out_pin_node[0], + node, type); + if (err < 0) + return err; + if (! err) + continue; + } + } + return 0; +} + +/* + * build mixer controls + */ +static int build_generic_controls(struct hda_codec *codec) +{ + int err; + + if ((err = build_input_controls(codec)) < 0 || + (err = build_output_controls(codec)) < 0 || + (err = build_loopback_controls(codec)) < 0) + return err; + + return 0; +} + +/* + * PCM + */ +static struct hda_pcm_stream generic_pcm_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, +}; + +static int generic_pcm2_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct hda_gspec *spec = codec->spec; + + snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format); + snd_hda_codec_setup_stream(codec, spec->dac_node[1]->nid, + stream_tag, 0, format); + return 0; +} + +static int generic_pcm2_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hda_gspec *spec = codec->spec; + + snd_hda_codec_cleanup_stream(codec, hinfo->nid); + snd_hda_codec_cleanup_stream(codec, spec->dac_node[1]->nid); + return 0; +} + +static int build_generic_pcms(struct hda_codec *codec) +{ + struct hda_gspec *spec = codec->spec; + struct hda_pcm *info = &spec->pcm_rec; + + if (! spec->dac_node[0] && ! spec->adc_node) { + snd_printd("hda_generic: no PCM found\n"); + return 0; + } + + codec->num_pcms = 1; + codec->pcm_info = info; + + info->name = "HDA Generic"; + if (spec->dac_node[0]) { + info->stream[0] = generic_pcm_playback; + info->stream[0].nid = spec->dac_node[0]->nid; + if (spec->dac_node[1]) { + info->stream[0].ops.prepare = generic_pcm2_prepare; + info->stream[0].ops.cleanup = generic_pcm2_cleanup; + } + } + if (spec->adc_node) { + info->stream[1] = generic_pcm_playback; + info->stream[1].nid = spec->adc_node->nid; + } + + return 0; +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static int generic_check_power_status(struct hda_codec *codec, hda_nid_t nid) +{ + struct hda_gspec *spec = codec->spec; + return snd_hda_check_amp_list_power(codec, &spec->loopback, nid); +} +#endif + + +/* + */ +static struct hda_codec_ops generic_patch_ops = { + .build_controls = build_generic_controls, + .build_pcms = build_generic_pcms, + .free = snd_hda_generic_free, +#ifdef CONFIG_SND_HDA_POWER_SAVE + .check_power_status = generic_check_power_status, +#endif +}; + +/* + * the generic parser + */ +int snd_hda_parse_generic_codec(struct hda_codec *codec) +{ + struct hda_gspec *spec; + int err; + + if(!codec->afg) + return 0; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) { + printk(KERN_ERR "hda_generic: can't allocate spec\n"); + return -ENOMEM; + } + codec->spec = spec; + INIT_LIST_HEAD(&spec->nid_list); + + if ((err = build_afg_tree(codec)) < 0) + goto error; + + if ((err = parse_input(codec)) < 0 || + (err = parse_output(codec)) < 0) + goto error; + + codec->patch_ops = generic_patch_ops; + + return 0; + + error: + snd_hda_generic_free(codec); + return err; +} +EXPORT_SYMBOL(snd_hda_parse_generic_codec); |