summaryrefslogtreecommitdiff
path: root/kernel/drv/oss_hdaudio/hdaudio_generic.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/drv/oss_hdaudio/hdaudio_generic.c')
-rw-r--r--kernel/drv/oss_hdaudio/hdaudio_generic.c868
1 files changed, 868 insertions, 0 deletions
diff --git a/kernel/drv/oss_hdaudio/hdaudio_generic.c b/kernel/drv/oss_hdaudio/hdaudio_generic.c
new file mode 100644
index 0000000..2ea9811
--- /dev/null
+++ b/kernel/drv/oss_hdaudio/hdaudio_generic.c
@@ -0,0 +1,868 @@
+/*
+ * Purpose: Default mixer/control panel driver for HDA codecs
+ *
+ * This generic driver is used to create mixer/control panels for HDaudio
+ * codec chips that don't have any dedicated driver available.
+ *
+ * This driver will obtain the widget definitions from the codec and then
+ * try to guess a mixer layout that makes some sense. However this approach
+ * works properly only with a small set of codecs.
+ *
+ * Most codecs are unbearably complex and provide loads of redundant
+ * functionality. The generic driver approach will not properly work with them
+ * because the mixer (GUI) layout will become too large to fit on any screen.
+ * In addition such automatically generated mixer controls will not make any
+ * sense to the users. So in the future the only possible approach will be
+ * creating dedicated mixer drivers for all possible codecs in the market.
+ * Unfortunately in some cases the driver may even need to be motherboard
+ * specific. Apparently this is going to be enormous task.
+ */
+/*
+ *
+ * This file is part of Open Sound System.
+ *
+ * Copyright (C) 4Front Technologies 1996-2008.
+ *
+ * This this source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ */
+
+#include "oss_hdaudio_cfg.h"
+#include "hdaudio.h"
+#include "hdaudio_codec.h"
+
+extern int hdaudio_snoopy;
+extern int hdaudio_jacksense;
+extern int hdaudio_noskip;
+
+static int
+count_linked_controls (hdaudio_mixer_t * mixer, codec_t * codec,
+ widget_t * widget, int recursive)
+{
+/*
+ * This function counts the number of mixer control elements this
+ * widget has.
+ * If recursive==1 then control counts of the previous widgets in the
+ * processing chain will be counted if number of inputs is exactly 1.
+ * Input sources are not checked if number of connections is larger than 1
+ * because separate mixer group is required for such widgets.
+ *
+ * Note! The policies used by this function must match exactly the policies
+ * used by follow_widget_chain()
+ */
+ int count = 0;
+
+ if (widget->skip)
+ return 0;
+
+ /*
+ * Output amp?
+ */
+ if (widget->widget_caps & WCAP_OUTPUT_AMP_PRESENT)
+ count += 1;
+
+ /*
+ * Input amp(s)?
+ */
+ if (widget->widget_caps & WCAP_INPUT_AMP_PRESENT)
+ {
+ if (widget->wid_type == NT_MIXER)
+ count += widget->nconn;
+ else
+ count++;
+ }
+
+ /*
+ * Input selector?
+ */
+ if (widget->wid_type == NT_SELECT && widget->nconn > 1)
+ count += 1;
+
+ if (recursive)
+ if (widget->nconn == 1) /* Exactly one input wource */
+ count +=
+ count_linked_controls (mixer, codec,
+ &codec->widgets[widget->connections[0]],
+ recursive);
+
+ return count;
+}
+
+/*ARGSUSED*/
+static int
+attach_amplifiers (int dev, hdaudio_mixer_t * mixer, codec_t * codec,
+ widget_t * widget, int group, int group_mode)
+{
+ int i, cnum, ninputs;
+ int g = group;
+ int use_mutegroup = 0;
+ oss_mixext *ent;
+
+/*
+ * Control for input amplifier(s)
+ */
+ if (widget->widget_caps & WCAP_INPUT_AMP_PRESENT)
+ {
+ if (widget->wid_type == NT_MIXER)
+ ninputs = widget->nconn;
+ else
+ ninputs = 1;
+
+ /*
+ * Check if it's possible to save horizontal space by creating a separate
+ * mute group. In this way the names of mute selectors become shorter.
+ */
+ if (!(widget->inamp_caps & ~AMPCAP_MUTE)
+ && (widget->inamp_caps & AMPCAP_MUTE) && ninputs > 2)
+ {
+ use_mutegroup = 1;
+ if ((g =
+ mixer_ext_create_group (mixer->mixer_dev, group, "mute")) < 0)
+ return g;
+ }
+
+ for (i = 0; i < ninputs; i++) /* All inputs */
+ {
+ char tmpname[32], tmp[40];
+ char *name = codec->widgets[widget->connections[i]].name;
+
+ if (ninputs == 1) /* Hide name */
+ name = "-";
+
+ if (codec->widgets[widget->connections[i]].skip)
+ continue;
+
+ if (widget->inamp_caps & ~AMPCAP_MUTE) /* Supports gain control */
+ {
+ int typ, num, maxval, val, range, step;
+ range =
+ ((widget->
+ outamp_caps >> AMPCAP_NUMSTEPS_SHIFT) &
+ AMPCAP_NUMSTEPS_MASK) + 1;
+ step =
+ ((widget->
+ outamp_caps >> AMPCAP_STEPSIZE_SHIFT) &
+ AMPCAP_STEPSIZE_MASK) + 1;
+
+ if (step > 20 /* 5dB */ && range < 5)
+ {
+ create_ingain_selector (mixer, codec, widget, group, i,
+ name);
+ continue;
+ }
+ maxval = hdaudio_amp_maxval (widget->inamp_caps);
+
+ if (widget->widget_caps & WCAP_STEREO)
+ {
+ typ = MIXT_STEREOSLIDER16;
+ num = MIXNUM (widget, CT_INSTEREO, i);
+ }
+ else
+ {
+ typ = MIXT_MONOSLIDER16;
+ num = MIXNUM (widget, CT_INMONO, i);
+ }
+
+ if (hdaudio_snoopy > 0)
+ {
+ sprintf (tmp, "%s:R%x", name, widget->wid);
+ name = tmp;
+ }
+
+ if ((cnum = mixer_ext_create_control (mixer->mixer_dev,
+ group,
+ num,
+ hdaudio_set_control,
+ typ,
+ name, maxval,
+ MIXF_READABLE |
+ MIXF_WRITEABLE |
+ MIXF_CENTIBEL)) < 0)
+ return cnum;
+
+ /* Copy RGB color */
+ if (widget->rgbcolor != 0)
+ if ((ent = mixer_find_ext (dev, cnum)) != NULL)
+ ent->rgbcolor = widget->rgbcolor;
+
+ /* Setup initial volume */
+ val = (maxval * 8) / 10; /* 80% of the maximum */
+ val = val | (val << 16);
+
+ hdaudio_set_control (mixer->mixer_dev, num, SNDCTL_MIX_WRITE,
+ val);
+ continue; /* Skip to the next input */
+ }
+
+ if (widget->inamp_caps & AMPCAP_MUTE) /* Supports only mute */
+ {
+ if (use_mutegroup)
+ strcpy (tmpname, name);
+ else
+ sprintf (tmpname, "%s-mute", name);
+ name = tmpname;
+
+ if (hdaudio_snoopy > 0)
+ {
+ sprintf (tmp, "%s:Q%x", name, widget->wid);
+ name = tmp;
+ }
+
+ if ((cnum = mixer_ext_create_control (mixer->mixer_dev,
+ g,
+ MIXNUM (widget,
+ CT_INMUTE, i),
+ hdaudio_set_control,
+ MIXT_MUTE, name, 2,
+ MIXF_READABLE |
+ MIXF_WRITEABLE)) < 0)
+ return cnum;
+ /* Copy RGB color */
+ if (widget->rgbcolor != 0)
+ if ((ent = mixer_find_ext (dev, cnum)) != NULL)
+ ent->rgbcolor = widget->rgbcolor;
+
+ hdaudio_set_control (mixer->mixer_dev,
+ MIXNUM (widget, CT_INMUTE, i),
+ SNDCTL_MIX_WRITE, 0);
+ }
+
+ }
+ }
+
+/*
+ * Output amplifier control
+ */
+
+ if (widget->widget_caps & WCAP_OUTPUT_AMP_PRESENT)
+ {
+ char tmp[32];
+ char *name = "-";
+
+ if (hdaudio_snoopy)
+ name = "outamp";
+
+ if (widget->outamp_caps & ~AMPCAP_MUTE) /* Has gain control */
+ {
+ int range, step, typ, num, maxval, val;
+ range =
+ ((widget->
+ outamp_caps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) +
+ 1;
+ step =
+ ((widget->
+ outamp_caps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) +
+ 1;
+
+ if (step > 20 /* 5dB */ && range < 5)
+ {
+ create_outgain_selector (mixer, widget, group, name);
+ }
+ else
+ {
+
+ maxval = hdaudio_amp_maxval (widget->outamp_caps);
+
+ if (widget->widget_caps & WCAP_STEREO)
+ {
+ typ = MIXT_STEREOSLIDER16;
+ num = MIXNUM (widget, CT_OUTSTEREO, 0);
+ }
+ else
+ {
+ typ = MIXT_MONOSLIDER16;
+ num = MIXNUM (widget, CT_OUTMONO, 0);
+ }
+
+ if (hdaudio_snoopy > 0)
+ {
+ sprintf (tmp, "%s:V%x", name, widget->wid);
+ name = tmp;
+ }
+ else
+ {
+ sprintf (tmp, "%s", widget->name);
+ name = tmp;
+ }
+
+ if ((cnum = mixer_ext_create_control (mixer->mixer_dev,
+ group,
+ num, hdaudio_set_control,
+ typ,
+ name, maxval,
+ MIXF_READABLE |
+ MIXF_WRITEABLE |
+ MIXF_CENTIBEL)) < 0)
+ return cnum;
+
+ /* Copy RGB color */
+ if (widget->rgbcolor != 0)
+ if ((ent = mixer_find_ext (dev, cnum)) != NULL)
+ ent->rgbcolor = widget->rgbcolor;
+
+ /* setup volume */
+ val = (maxval * 8) / 10; /* 80% of the maximum */
+ val = val | (val << 16);
+ hdaudio_set_control (mixer->mixer_dev, num, SNDCTL_MIX_WRITE,
+ val);
+ }
+ }
+ else if (widget->outamp_caps & AMPCAP_MUTE) /* Only mute control */
+ {
+ char tmpname[32];
+ name = "mute";
+ if (hdaudio_snoopy > 0)
+ {
+ sprintf (tmpname, "%s:U%x", name, widget->wid);
+ name = tmpname;
+ }
+
+ if ((cnum = mixer_ext_create_control (mixer->mixer_dev,
+ group,
+ MIXNUM (widget,
+ CT_OUTMUTE, 0),
+ hdaudio_set_control,
+ MIXT_MUTE, name, 2,
+ MIXF_READABLE |
+ MIXF_WRITEABLE)) < 0)
+ return cnum;
+
+ /* Copy RGB color */
+ if (widget->rgbcolor != 0)
+ if ((ent = mixer_find_ext (dev, cnum)) != NULL)
+ ent->rgbcolor = widget->rgbcolor;
+
+ hdaudio_set_control (mixer->mixer_dev,
+ MIXNUM (widget, CT_OUTMUTE, 0),
+ SNDCTL_MIX_WRITE, 0);
+ }
+ }
+
+ return 0;
+}
+
+/*ARGSUSED*/
+static int
+attach_selector (int dev, hdaudio_mixer_t * mixer, codec_t * codec,
+ widget_t * widget, int group, int group_mode)
+{
+ unsigned int c, b;
+ char *name = "src";
+
+ int i, ctl;
+ char tmp[256], *t = tmp;
+ oss_mixext *ext;
+ int count = 0;
+
+
+ /*
+ * first check to see if there are more that 2 valid options to create a
+ * selector for.
+ */
+ for (i = 0; i < widget->nconn; i++)
+ if (!codec->widgets[widget->connections[i]].skip
+ && codec->widgets[widget->connections[i]].sensed_pin != PIN_OUT)
+ count++;
+
+ if (count < 2)
+ return 0;
+
+
+ name = widget->name;
+
+ if (corb_read (mixer, widget->cad, widget->wid, 0, GET_SELECTOR, 0, &c, &b))
+ widget->current_selector = c;
+
+ if (hdaudio_snoopy > 0)
+ {
+ sprintf (tmp, "%s:%x", name, widget->wid);
+ name = tmp;
+ }
+
+
+ if ((ctl = mixer_ext_create_control (mixer->mixer_dev,
+ group,
+ MIXNUM (widget, CT_SELECT, 0),
+ hdaudio_set_control,
+ MIXT_ENUM,
+ name,
+ widget->nconn,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+
+ *tmp = 0;
+ ext = mixer_find_ext (mixer->mixer_dev, ctl);
+
+ if (ext == NULL)
+ {
+ cmn_err (CE_WARN, "Cannot locate the mixer extension (a)\n");
+ return OSS_EIO;
+ }
+
+ /* Copy RGB color */
+ ext->rgbcolor = widget->rgbcolor;
+
+ memset (ext->enum_present, 0, sizeof (ext->enum_present));
+
+ for (i = 0; i < widget->nconn; i++)
+ {
+ char *s;
+
+ /*
+ * ensure that the connection list has a valid widget id - some
+ * devices have bogus connection lists
+ */
+ if (codec->widgets[widget->connections[i]].wid < codec->first_node)
+ continue;
+
+ s = codec->widgets[widget->connections[i]].name;
+ if (strlen (tmp) + strlen (s) + 1 < sizeof (tmp) - 1)
+ {
+ if (*tmp != 0)
+ *t++ = ' ';
+ strcpy (t, s);
+ if (hdaudio_snoopy > 0)
+ sprintf (t, "A%s:%x", s,
+ mixer->codecs[widget->cad]->widgets[widget->
+ connections[i]].wid);
+ t += strlen (t);
+
+ /*
+ * Show only widgets that are not marked to be ignored.
+ * Also hide I/O pins that are known to be outputs.
+ */
+ if (!codec->widgets[widget->connections[i]].skip
+ && codec->widgets[widget->connections[i]].sensed_pin != PIN_OUT)
+ ext->enum_present[i / 8] |= (1 << (i % 8));
+ else
+ {
+ if (widget->current_selector == i)
+ widget->current_selector++;
+ }
+ }
+ }
+ mixer_ext_set_strings (mixer->mixer_dev, ctl, tmp, 0);
+
+ if (widget->current_selector >= widget->nconn)
+ widget->current_selector = 0;
+ corb_write (mixer, widget->cad, widget->wid, 0, SET_SELECTOR,
+ widget->current_selector);
+
+ return 0;
+}
+
+static int
+follow_widget_chain (int dev, hdaudio_mixer_t * mixer, codec_t * codec,
+ widget_t * widget, int group)
+{
+ int err;
+
+ if (widget->used) /* Already handled */
+ return 0;
+
+ widget->used = 1;
+
+ if (widget->nconn >= 1)
+ if ((err =
+ follow_widget_chain (dev, mixer, codec,
+ &codec->widgets[widget->connections[0]],
+ group)) < 0)
+ return err;
+
+ if ((err = attach_amplifiers (dev, mixer, codec, widget, group, 1)) < 0)
+ return err;
+
+ if (widget->wid_type == NT_SELECT)
+ if ((err = attach_selector (dev, mixer, codec, widget, group, 1)) < 0)
+ return err;
+
+ return 0;
+}
+
+static int
+attach_pin_widget (int dev, hdaudio_mixer_t * mixer, codec_t * codec,
+ widget_t * widget, int parent_group)
+{
+ int group = parent_group, g;
+ unsigned int b, c, conf;
+ int i, ctl, err;
+ int inselects = 0, outselects = 0, linked_controls = 0;
+ int num_amps = 0;
+ char tmp[256], *t = tmp;
+ oss_mixext *ext;
+
+ if (widget->pincaps & PINCAP_OUTPUT_CAPABLE)
+ {
+ outselects = widget->nconn;
+
+ if (widget->nconn == 1) /* Exactly one connection */
+ {
+ linked_controls =
+ count_linked_controls (mixer, codec,
+ &codec->widgets[widget->connections[0]],
+ 1);
+ }
+ }
+
+ if (widget->pincaps & PINCAP_INPUT_CAPABLE)
+ {
+ if (!(widget->widget_caps & WCAP_DIGITAL)) /* Analog pin */
+ {
+ inselects = 1;
+ }
+ }
+
+ if (widget->widget_caps & WCAP_INPUT_AMP_PRESENT)
+ {
+ num_amps++;
+ }
+
+ if (widget->widget_caps & WCAP_OUTPUT_AMP_PRESENT)
+ {
+ num_amps++;
+ }
+
+ if ((inselects + outselects > 1) || num_amps > 0 || linked_controls > 0) /* Have something to control */
+ {
+ if (widget->color[0] == 0) /* Empty name */
+ sprintf (widget->color, "jack%02x", widget->wid);
+ if ((g =
+ mixer_ext_create_group (mixer->mixer_dev, group,
+ widget->color)) < 0)
+ return g;
+
+ if (corb_read
+ (mixer, widget->cad, widget->wid, 0, GET_SELECTOR, 0, &c, &b))
+ widget->current_selector = c;
+
+ if (inselects + outselects > 1)
+ {
+ if ((ctl = mixer_ext_create_control (mixer->mixer_dev,
+ g,
+ MIXNUM (widget, CT_SELECT, 0),
+ hdaudio_set_control,
+ MIXT_ENUM,
+ "mode",
+ inselects + outselects,
+ MIXF_READABLE |
+ MIXF_WRITEABLE)) < 0)
+ return ctl;
+
+ *tmp = 0;
+ ext = mixer_find_ext (mixer->mixer_dev, ctl);
+
+ if (ext == NULL)
+ {
+ cmn_err (CE_WARN, "Cannot locate the mixer extension (b)\n");
+ return OSS_EIO;
+ }
+ /* Copy RGB color */
+ ext->rgbcolor = widget->rgbcolor;
+
+ memset (ext->enum_present, 0, sizeof (ext->enum_present));
+
+ for (i = 0; i < widget->nconn; i++)
+ {
+ char *s;
+
+ s = codec->widgets[widget->connections[i]].name;
+ if (strlen (tmp) + strlen (s) + 1 < sizeof (tmp) - 1)
+ {
+ if (*tmp != 0)
+ *t++ = ' ';
+ strcpy (t, s);
+ if (hdaudio_snoopy > 0)
+ sprintf (t, "A%s:%x", s,
+ mixer->codecs[widget->cad]->widgets[widget->
+ connections
+ [i]].wid);
+ t += strlen (t);
+
+ /*
+ * Show only widgets that are not marked to be ignored.
+ */
+ if (!codec->widgets[widget->connections[i]].skip)
+ ext->enum_present[i / 8] |= (1 << (i % 8));
+ else
+ {
+ if (widget->current_selector == i)
+ widget->current_selector++;
+ }
+ }
+ }
+
+/*
+ * Use the default sequence as an index to the output source selectors.
+ */
+ if (widget->sensed_pin == PIN_OUT)
+ if (corb_read
+ (mixer, widget->cad, widget->wid, 0, GET_CONFIG_DEFAULT, 0,
+ &conf, &b))
+ {
+ int association, sequence;
+
+ association = (conf >> 4) & 0x0f;
+ sequence = conf & 0x0f;
+
+ if (association != 0)
+ {
+ widget->current_selector = sequence;
+ }
+
+ }
+
+ if (widget->current_selector >= widget->nconn)
+ widget->current_selector = 0;
+
+ if (inselects > 0) /* Input capable */
+ {
+ char *s;
+
+ i = widget->nconn;
+ s = widget->name;
+
+ if (*tmp != 0)
+ *t++ = ' ';
+
+ strcpy (t, "input");
+
+ t += strlen (t);
+ ext->enum_present[i / 8] |= (1 << (i % 8));
+ i++;
+
+ if (widget->pin_type == PIN_IN)
+ widget->current_selector = widget->nconn;
+ }
+
+ mixer_ext_set_strings (mixer->mixer_dev, ctl, tmp, 0);
+ }
+
+ hdaudio_set_control (mixer->mixer_dev,
+ MIXNUM (widget, CT_SELECT, 0),
+ SNDCTL_MIX_WRITE, widget->current_selector);
+
+ if ((err = attach_amplifiers (dev, mixer, codec, widget, g, 0)) < 0)
+ return err;
+
+ if (widget->nconn == 1)
+ if ((err =
+ follow_widget_chain (dev, mixer, codec,
+ &codec->widgets[widget->connections[0]],
+ g)) < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int
+attach_record_widget (int dev, hdaudio_mixer_t * mixer, codec_t * codec,
+ widget_t * widget, int parent_group)
+{
+ int group = parent_group, g;
+ int err;
+ int linked_controls = 0;
+ int num_amps = 0;
+
+ if (widget->widget_caps & WCAP_INPUT_AMP_PRESENT)
+ {
+ num_amps++;
+ }
+
+ if (widget->widget_caps & WCAP_OUTPUT_AMP_PRESENT)
+ {
+ num_amps++;
+ }
+
+ if (widget->nconn == 1) /* Exactly one connection */
+ {
+ linked_controls =
+ count_linked_controls (mixer, codec,
+ &codec->widgets[widget->connections[0]], 1);
+ }
+
+ if (num_amps > 0 || linked_controls > 1) /* Have something to control */
+ {
+ if ((g =
+ mixer_ext_create_group (mixer->mixer_dev, group,
+ widget->name)) < 0)
+ return g;
+
+ if (widget->nconn == 1)
+ if ((err =
+ follow_widget_chain (dev, mixer, codec,
+ &codec->widgets[widget->connections[0]],
+ g)) < 0)
+ return err;
+
+ if ((err = attach_amplifiers (dev, mixer, codec, widget, g, 0)) < 0)
+ return err;
+ if ((err = attach_selector (dev, mixer, codec, widget, g, 0)) < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int
+attach_misc_widget (int dev, hdaudio_mixer_t * mixer, codec_t * codec,
+ widget_t * widget, int parent_group)
+{
+ int err;
+ int nselect = 0;
+ int num_amps = 0;
+
+ if (widget->widget_caps & WCAP_INPUT_AMP_PRESENT)
+ {
+ num_amps++;
+ }
+
+ if (widget->widget_caps & WCAP_OUTPUT_AMP_PRESENT)
+ {
+ num_amps++;
+ }
+
+ if ((widget->wid_type == NT_SELECT || widget->wid_type == NT_MIXER)
+ && widget->nconn > 0)
+ nselect = widget->nconn;
+
+ if (num_amps > 0 || nselect > 1) /* Have something to control */
+ {
+#if 0
+ if ((g =
+ mixer_ext_create_group (mixer->mixer_dev, group,
+ widget->name)) < 0)
+ return g;
+#endif
+ if ((err =
+ attach_amplifiers (dev, mixer, codec, widget, parent_group,
+ 0)) < 0)
+ return err;
+
+ if (nselect > 1)
+ if ((err =
+ attach_selector (dev, mixer, codec, widget, parent_group,
+ 0)) < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+int
+hdaudio_generic_mixer_init (int dev, hdaudio_mixer_t * mixer, int cad,
+ int parent_group)
+{
+ unsigned int vendorid, b;
+ int err;
+ int wid, n;
+ codec_t *codec;
+ widget_t *widget;
+ int group = parent_group;
+
+ if (mixer->codecs[cad] == NULL)
+ {
+ cmn_err (CE_WARN, "Bad codec %d\n", cad);
+ return OSS_EIO;
+ }
+ codec = mixer->codecs[cad];
+
+ if (!corb_read (mixer, cad, 0, 0, GET_PARAMETER, HDA_VENDOR, &vendorid, &b))
+ {
+ cmn_err (CE_WARN, "Cannot get codec ID\n");
+ return OSS_EIO;
+ }
+
+/*
+ * First handle all the PIN widgets
+ */
+ n = 0;
+
+ for (wid = 0; wid < codec->nwidgets; wid++)
+ {
+ widget = &codec->widgets[wid];
+
+ if (widget->wid_type != NT_PIN) /* Not a pin widget */
+ continue;
+
+ widget->used = 1;
+
+ if (widget->skip) /* Unused/unconnected PIN widget */
+ {
+ continue;
+ }
+
+ if ((n++ % 3) == 0)
+ {
+ if ((group =
+ mixer_ext_create_group (mixer->mixer_dev, parent_group,
+ "jack")) < 0)
+ return group;
+ }
+
+
+ if ((err = attach_pin_widget (dev, mixer, codec, widget, group)) < 0)
+ return err;
+ }
+
+/*
+ * Next handle all the ADC widgets
+ */
+ n = 0;
+ for (wid = 0; wid < codec->nwidgets; wid++)
+ {
+ widget = &codec->widgets[wid];
+
+ if (widget->wid_type != NT_ADC) /* Not a pin widget */
+ continue;
+
+ if (widget->skip)
+ continue;
+
+ widget->used = 1;
+
+ if ((n++ % 3) == 0)
+ {
+ if ((group =
+ mixer_ext_create_group (mixer->mixer_dev, parent_group,
+ "record")) < 0)
+ return group;
+ }
+
+ if ((err = attach_record_widget (dev, mixer, codec, widget, group)) < 0)
+ return err;
+ }
+
+/*
+ * Finally handle all the widgets that have not been attached yet
+ */
+
+ n = 0;
+ for (wid = 0; wid < codec->nwidgets; wid++)
+ {
+ widget = &codec->widgets[wid];
+
+ if (widget->skip)
+ continue;
+
+ if (widget->used) /* Already handled */
+ continue;
+
+ widget->used = 1;
+
+ if (count_linked_controls (mixer, codec, widget, 0) > 0)
+ if ((n++ % 4) == 0)
+ {
+ if ((group =
+ mixer_ext_create_group (mixer->mixer_dev, parent_group,
+ "misc")) < 0)
+ return group;
+ }
+
+ if ((err = attach_misc_widget (dev, mixer, codec, widget, group)) < 0)
+ return err;
+ }
+ return 0;
+}