summaryrefslogtreecommitdiff
path: root/kernel/drv/oss_hdaudio/hdaudio_codec.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/drv/oss_hdaudio/hdaudio_codec.c')
-rw-r--r--kernel/drv/oss_hdaudio/hdaudio_codec.c3580
1 files changed, 3580 insertions, 0 deletions
diff --git a/kernel/drv/oss_hdaudio/hdaudio_codec.c b/kernel/drv/oss_hdaudio/hdaudio_codec.c
new file mode 100644
index 0000000..fb29840
--- /dev/null
+++ b/kernel/drv/oss_hdaudio/hdaudio_codec.c
@@ -0,0 +1,3580 @@
+/*
+ * Purpose: Codec handling for Intel High Definition Audio (HDA/Azalia).
+ */
+/*
+ *
+ * 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"
+#include "hdaudio_codecids.h"
+
+extern int hdaudio_snoopy;
+extern int hdaudio_jacksense;
+extern int hdaudio_noskip;
+
+static codec_t NULL_codec = { 0 }; /* TODO: Temporary workaround - to be removed */
+
+
+/* Si3055 functions (implemented in hdaudio_si3055.c) */
+extern void hdaudio_si3055_endpoint_init(hdaudio_mixer_t *mixer, int cad);
+extern void hdaudio_si3055_set_rate(hdaudio_mixer_t *mixer, int cad, int rate);
+extern int hdaudio_si3055_set_offhook(hdaudio_mixer_t *mixer, int cad, int offhook);
+
+
+static int attach_codec (hdaudio_mixer_t * mixer, int cad, char *hw_info,
+ unsigned int pci_subdevice, int group_type);
+int
+hdaudio_mixer_get_outendpoints (hdaudio_mixer_t * mixer,
+ hdaudio_endpointinfo_t ** endpoints, int size)
+{
+ int i;
+
+ if (size != sizeof (hdaudio_endpointinfo_t))
+ {
+ cmn_err (CE_WARN, "Bad endpoint size\n");
+ return OSS_EIO;
+ }
+
+ *endpoints = (hdaudio_endpointinfo_t *) & mixer->outendpoints;
+
+ for (i = 0; i < mixer->num_outendpoints; i++)
+ {
+ hdaudio_endpointinfo_t *ep = *endpoints + i;
+
+ ep->skip = mixer->codecs[ep->cad]->widgets[ep->base_wid].skip;
+
+ if (hdaudio_snoopy)
+ {
+ char *s = ep->name + strlen(ep->name);
+ sprintf(s, ":%d:%d", ep->cad, ep->base_wid);
+ }
+ }
+ return mixer->num_outendpoints;
+}
+
+int
+hdaudio_mixer_get_inendpoints (hdaudio_mixer_t * mixer,
+ hdaudio_endpointinfo_t ** endpoints, int size)
+{
+ int i;
+
+ if (size != sizeof (hdaudio_endpointinfo_t))
+ {
+ cmn_err (CE_WARN, "Bad endpoint size\n");
+ return OSS_EIO;
+ }
+
+ *endpoints = (hdaudio_endpointinfo_t *) & mixer->inendpoints;
+
+ for (i = 0; i < mixer->num_inendpoints; i++)
+ {
+ hdaudio_endpointinfo_t *ep = *endpoints + i;
+
+ ep->skip = mixer->codecs[ep->cad]->widgets[ep->base_wid].skip;
+
+ if (hdaudio_snoopy)
+ {
+ char *s = ep->name + strlen(ep->name);
+ sprintf(s, ":%d:%d", ep->cad, ep->base_wid);
+ }
+ }
+ return mixer->num_inendpoints;
+}
+
+/*ARGSUSED*/
+static int
+hda_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg)
+{
+ if (cmd == SOUND_MIXER_READ_DEVMASK ||
+ cmd == SOUND_MIXER_READ_RECMASK || cmd == SOUND_MIXER_READ_RECSRC ||
+ cmd == SOUND_MIXER_READ_STEREODEVS)
+ return *arg = 0;
+
+ return OSS_EINVAL;
+}
+
+static mixer_driver_t hda_mixer_driver = {
+ hda_mixer_ioctl
+};
+
+
+static void
+propagate_names (hdaudio_mixer_t * mixer)
+{
+ int c, w;
+ int i;
+/*
+ * Check if the same name can be used for all the widgets on an unique path.
+ */
+
+ for (i = 0; i < 20; i++)
+ for (c = 0; c < mixer->ncodecs; c++)
+ if (mixer->codecs[c] != &NULL_codec)
+ {
+ for (w = 1; w < mixer->codecs[c]->nwidgets; w++)
+ {
+ widget_t *widget = &mixer->codecs[c]->widgets[w];
+ widget_t *src_widget =
+ &mixer->codecs[c]->widgets[widget->connections[0]];
+
+ if (widget->nconn != 1)
+ continue;
+
+#if 0
+ if (src_widget->wid_type == NT_PIN
+ || src_widget->wid_type == NT_DAC)
+ continue;
+
+ if (src_widget->wid_type != NT_MIXER && src_widget->wid_type != NT_VENDOR) /* Mixer */
+ continue;
+#endif
+
+ strcpy (widget->name, src_widget->name);
+
+ /*
+ * Copy widget's RGB color to all widgets in the path
+ */
+ if (widget->rgbcolor == 0)
+ widget->rgbcolor = src_widget->rgbcolor;
+ }
+ }
+#if 0
+ // Debugging code
+ for (c = 0; c < mixer->ncodecs; c++)
+ if (mixer->codecs[c] != &NULL_codec)
+ for (w = 1; w < mixer->codecs[c]->nwidgets; w++)
+ {
+ widget_t *widget = &mixer->codecs[c]->widgets[w];
+
+ cmn_err(CE_CONT, "w= %02x rgb=%06x: %s (%s)\n", w, widget->rgbcolor, widget->name, widget->color);
+ }
+#endif
+}
+
+static void
+check_names (hdaudio_mixer_t * mixer)
+{
+ int c, c2, w, w2;
+ int n, start;
+
+/*
+ * Make sure all widgets have unique names.
+ */
+ for (c = 0; c < mixer->ncodecs; c++)
+ if (mixer->codecs[c] != &NULL_codec)
+ {
+ for (w = 1; w < mixer->codecs[c]->nwidgets; w++)
+ {
+ char tmp[16];
+ n = 0;
+ if (mixer->codecs[c]->widgets[w].skip) /* Not available */
+ continue;
+
+ strcpy (tmp, mixer->codecs[c]->widgets[w].name);
+
+ start = w + 1;
+
+ for (c2 = c; c2 < mixer->ncodecs; c2++)
+ {
+ for (w2 = start; w2 < mixer->codecs[c2]->nwidgets; w2++)
+ {
+ if (mixer->codecs[c2]->widgets[w2].skip) /* Not available */
+ continue;
+
+ if (strcmp (tmp, mixer->codecs[c2]->widgets[w2].name) ==
+ 0)
+ n++;
+ }
+
+ start = 1;
+ }
+
+ if (n > 0) /* Duplicates found */
+ {
+ n = 0;
+ start = w;
+ for (c2 = c; c2 < mixer->ncodecs; c2++)
+ {
+ for (w2 = start; w2 < mixer->codecs[c2]->nwidgets; w2++)
+ {
+ if (mixer->codecs[c2]->widgets[w2].skip) /* Not available */
+ continue;
+
+ if (strcmp (tmp, mixer->codecs[c2]->widgets[w2].name)
+ == 0)
+ {
+ n++;
+ sprintf (mixer->codecs[c2]->widgets[w2].name,
+ "%s%d", tmp, n);
+ }
+ }
+
+ start = 1;
+ }
+ }
+ }
+ }
+}
+
+int
+hdaudio_amp_maxval (unsigned int ampcaps)
+{
+ int step, range, maxval;
+
+ range = ((ampcaps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) + 1;
+ step = ((ampcaps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) + 1;
+
+ maxval = range * step * 25; /* Now in 0.01 dB steps */
+ maxval = (maxval / 10) - 1; /* Truncate to centibel (0.1 dB) scaling */
+
+ return maxval;
+}
+
+static int
+scaleout_vol (int v, unsigned int ampcaps)
+{
+ int step, range, maxval;
+
+ range = ((ampcaps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) + 1;
+ step = ((ampcaps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) + 1;
+
+ maxval = range * step * 25; /* Now in 0.01 dB steps */
+ maxval = (maxval / 10) - 1; /* Truncate to centibel (0.1 dB) scaling */
+ if (v > maxval)
+ v = maxval;
+
+ v *= 10; /* centibels -> millibels */
+
+ v = (v + (25 * step) / 2) / (25 * step);
+
+ if (v > 0x7f || v >= range)
+ {
+ v = range - 1;
+ }
+
+ if (v < 0)
+ v = 0;
+ return v;
+}
+
+static int
+scalein_vol (int v, unsigned int ampcaps)
+{
+ int step, range, maxval;
+
+ range = ((ampcaps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) + 1;
+ step = ((ampcaps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) + 1;
+
+ maxval = range * step * 25; /* Now in 0.01 dB steps */
+ maxval = (maxval / 10) - 1; /* Truncate to centibel (0.1 dB) scaling */
+
+ v *= step * 25; /* Convert to millibels */
+
+ v = v / 10 - 1; /* millibels -> centibels */
+
+ if (v > maxval)
+ {
+ v = maxval;
+ }
+ if (v < 0)
+ v = 0;
+
+ return v;
+}
+
+static int
+handle_insrcselect (hdaudio_mixer_t * mixer, widget_t * widget, int value)
+{
+/*
+ * Emulated (recording) source selection based on input amp mute controls.
+ *
+ * Mute all inputs other than the selected one.
+ */
+ int i;
+
+ widget->current_selector = value;
+
+ for (i = 0; i < widget->nconn; i++)
+ {
+ int v = (i == value) ? 0 : 0x80;
+
+ corb_write (mixer, widget->cad, widget->wid, 0,
+ SET_GAIN (0, 1, 1, 1, i), v);
+ }
+
+ return value;
+}
+
+int
+hdaudio_set_control (int dev, int ctrl, unsigned int cmd, int value)
+{
+ hdaudio_mixer_t *mixer = mixer_devs[dev]->devc;
+
+ unsigned int cad, wid, linked_wid, typ, ix, left, right, a, b, v;
+ widget_t *widget;
+
+ ix = ctrl & 0xff;
+ typ = (ctrl >> 8) & 0xff;
+ wid = (ctrl >> 16) & 0xff;
+ cad = (ctrl >> 24) & 0xff;
+
+ if (cad >= mixer->ncodecs)
+ return OSS_EIO;
+
+ if (wid >= mixer->codecs[cad]->nwidgets)
+ return OSS_EIO;
+
+ widget = &mixer->codecs[cad]->widgets[wid];
+
+ if (mixer->codecs[cad]->vendor_flags & VF_VAIO_HACK)
+ linked_wid = (wid == 0x02) ? 0x05 : ((wid == 0x05) ? 0x02 : 0);
+ else
+ linked_wid = 0;
+
+ if (cmd == SNDCTL_MIX_READ)
+ switch (typ)
+ {
+ case CT_INGAINSEL:
+ if (!corb_read (mixer, cad, wid, 0, GET_GAIN (0, 0), ix, &a, &b))
+ return OSS_EIO;
+ return a & 0x7f; // TODO: Handle mute
+ break;
+
+ case CT_INMONO:
+ if (!corb_read (mixer, cad, wid, 0, GET_GAIN (0, 0), ix, &a, &b))
+ return OSS_EIO;
+ if (a & 0x80)
+ left = 0;
+ else
+ left = scalein_vol (a, widget->inamp_caps);
+ return left | (left << 16);
+ break;
+
+ case CT_INSTEREO:
+ if (!corb_read (mixer, cad, wid, 0, GET_GAIN (0, 1), ix, &a, &b))
+ return OSS_EIO;
+ if (a & 0x80)
+ left = 0;
+ else
+ left = scalein_vol (a, widget->inamp_caps);
+ if (!corb_read (mixer, cad, wid, 0, GET_GAIN (0, 0), ix, &a, &b))
+ return OSS_EIO;
+ if (a & 0x80)
+ right = 0;
+ else
+ right = scalein_vol (a, widget->inamp_caps);
+ return left | (right << 16);
+ break;
+
+ case CT_INMUTE:
+ if (!corb_read (mixer, cad, wid, 0, GET_GAIN (0, 0), ix, &a, &b))
+ return OSS_EIO;
+ return (a >> 7) & 0x01;
+ break;
+
+ case CT_INSRC: /* Inverse mute */
+ if (!corb_read (mixer, cad, wid, 0, GET_GAIN (0, 0), ix, &a, &b))
+ return OSS_EIO;
+ return !((a >> 7) & 0x01);
+ break;
+
+ case CT_SELECT:
+ return widget->current_selector;
+ break;
+
+ case CT_INSRCSELECT: /* Emulated selector based on input mute controls */
+ return widget->current_selector;
+
+ case CT_OUTGAINSEL:
+ if (!corb_read (mixer, cad, wid, 0, GET_GAIN (1, 1), 0, &a, &b))
+ return OSS_EIO;
+ return a; // TODO: Handle mute
+ break;
+
+ case CT_OUTMONO:
+ if (!corb_read (mixer, cad, wid, 0, GET_GAIN (1, 1), 0, &a, &b))
+ return OSS_EIO;
+ left = a & 0x7f;
+ if (a & 0x80)
+ left = 0;
+ else
+ left = scalein_vol (a, widget->outamp_caps);
+ return left | (left << 16);
+ break;
+
+ case CT_OUTSTEREO:
+ if (!corb_read (mixer, cad, wid, 0, GET_GAIN (1, 1), 0, &a, &b))
+ return OSS_EIO;
+ left = a & 0x7f;
+ if (a & 0x80)
+ left = 0;
+ else
+ left = scalein_vol (a, widget->outamp_caps);
+ if (!corb_read (mixer, cad, wid, 0, GET_GAIN (1, 0), 0, &a, &b))
+ return OSS_EIO;
+ right = a & 0x7f;
+ if (a & 0x80)
+ right = 0;
+ else
+ right = scalein_vol (a, widget->outamp_caps);
+ return left | (right << 16);
+ break;
+
+ case CT_OUTMUTE:
+ if (!corb_read (mixer, cad, wid, 0, GET_GAIN (1, 0), 0, &a, &b))
+ return OSS_EIO;
+ return (a >> 7) & 0x01;
+ break;
+
+ default:
+ return OSS_EINVAL;
+ }
+
+ if (cmd == SNDCTL_MIX_WRITE)
+ {
+ switch (typ)
+ {
+ case CT_INMONO:
+ v = (value & 0xffff);
+ if (v == 0)
+ v = 0x80; /* Muted */
+ else
+ v = scaleout_vol (v, widget->inamp_caps);
+
+ corb_write (mixer, cad, wid, 0, SET_GAIN (0, 1, 1, 1, ix), v);
+ return value;
+ break;
+
+ case CT_INSTEREO:
+ v = (value & 0xffff);
+ if (v == 0)
+ v = 0x80; /* Muted */
+ else
+ v = scaleout_vol (v, widget->inamp_caps);
+
+ corb_write (mixer, cad, wid, 0, SET_GAIN (0, 1, 1, 0, ix), v);
+ v = ((value >> 16) & 0xffff);
+ if (v == 0)
+ v = 0x80; /* Muted */
+ else
+ v = scaleout_vol (v, widget->inamp_caps);
+ corb_write (mixer, cad, wid, 0, SET_GAIN (0, 1, 0, 1, ix), v);
+ return value;
+ break;
+
+ case CT_INGAINSEL:
+ v = (value & 0x7f);
+ // TODO: Handle mute
+ corb_write (mixer, cad, wid, 0, SET_GAIN (0, 1, 1, 0, ix), v);
+ corb_write (mixer, cad, wid, 0, SET_GAIN (0, 1, 0, 1, ix), v);
+ return value;
+ break;
+
+ case CT_INMUTE:
+ v = 0;
+ if (value)
+ v = 0x80;
+ corb_write (mixer, cad, wid, 0, SET_GAIN (0, 1, 1, 1, ix), v);
+ return value;
+ break;
+
+ case CT_INSRC: /* Inverse mute */
+ v = 0;
+ if (!value)
+ v = 0x80;
+ corb_write (mixer, cad, wid, 0, SET_GAIN (0, 1, 1, 1, ix), v);
+ return value;
+ break;
+
+ case CT_SELECT:
+ if (value < 0)
+ value = 0;
+ widget->current_selector = value;
+
+ if (value < widget->nconn)
+ {
+ /* Output source select */
+ corb_write (mixer, cad, wid, 0, SET_SELECTOR, value);
+ /* Enable output and HP amp. Set vref=Ground */
+ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0xc0);
+ }
+ else
+ {
+ /* Input select
+ * Program the correct VRef Values
+ */
+
+ if (widget->pin_type == PIN_IN) /* Line-in */
+ {
+ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x20); /*Ground*/
+ }
+ else /* Mic-in */
+ {
+ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x24); /*Vref=8
+ 0% */
+ }
+ }
+ return value;
+ break;
+
+ case CT_INSRCSELECT: /* Emulated selector based on input mute mask */
+ if (value < 0)
+ value = 0;
+ if (value >= widget->nconn)
+ value = widget->nconn;
+ return handle_insrcselect (mixer, widget, value);
+ break;
+
+ case CT_OUTGAINSEL:
+ // TODO: Handle mute
+ corb_write (mixer, cad, wid, 0, SET_GAIN (1, 0, 1, 0, ix), value);
+ corb_write (mixer, cad, wid, 0, SET_GAIN (1, 0, 0, 1, ix), value);
+ return value;
+ break;
+
+ case CT_OUTMONO:
+ v = (value & 0xffff);
+ if (v == 0)
+ v = 0x80; /* Muted */
+ else
+ v = scaleout_vol (v, widget->outamp_caps);
+ corb_write (mixer, cad, wid, 0, SET_GAIN (1, 0, 1, 0, ix), v);
+ if (linked_wid)
+ corb_write (mixer, cad, wid, 0, SET_GAIN (1, 0, 1, 0, ix), v);
+ return value;
+ break;
+
+ case CT_OUTSTEREO:
+ v = (value & 0xffff);
+ if (v == 0)
+ v = 0x80; /* Muted */
+ else
+ v = scaleout_vol (v, widget->outamp_caps);
+ corb_write (mixer, cad, wid, 0, SET_GAIN (1, 0, 1, 0, ix), v);
+ if (linked_wid)
+ corb_write (mixer, cad, linked_wid, 0, SET_GAIN (1, 0, 1, 0, ix), v);
+ v = ((value >> 16) & 0xffff);
+ if (v == 0)
+ v = 0x80; /* Muted */
+ else
+ v = scaleout_vol (v, widget->outamp_caps);
+ corb_write (mixer, cad, wid, 0, SET_GAIN (1, 0, 0, 1, ix), v);
+ if (linked_wid)
+ corb_write (mixer, cad, linked_wid, 0, SET_GAIN (1, 0, 0, 1, ix), v);
+ return value;
+ break;
+
+ case CT_OUTMUTE:
+ v = 0;
+ if (value)
+ v = 0x80;
+ corb_write (mixer, cad, wid, 0, SET_GAIN (1, 0, 1, 1, ix), v);
+ if (linked_wid)
+ corb_write (mixer, cad, linked_wid, 0, SET_GAIN (1, 0, 1, 1, ix), v);
+ return value;
+ break;
+
+ default:
+ return OSS_EINVAL;
+ }
+ }
+
+ return OSS_EINVAL;
+}
+
+static int
+perform_pin_sense (hdaudio_mixer_t * mixer)
+{
+ int cad, wid;
+ unsigned int a, b;
+ int plugged_in;
+ codec_t *codec;
+
+ for (cad = 0; cad < mixer->ncodecs; cad++)
+ if (mixer->codecs[cad] != &NULL_codec)
+ {
+ codec = mixer->codecs[cad];
+
+ for (wid = 1; wid < mixer->codecs[cad]->nwidgets; wid++)
+ if (mixer->codecs[cad]->widgets[wid].wid_type == NT_PIN) /* Pin complex */
+ {
+ widget_t *widget = &mixer->codecs[cad]->widgets[wid];
+
+ widget->impsense = -1;
+ widget->sensed_pin = widget->pin_type;
+
+ plugged_in = 1;
+
+ /* Load the sense information */
+ if ((widget->pincaps & PINCAP_JACKSENSE_CAPABLE)
+ || (widget->pincaps & PINCAP_IMPSENSE_CAPABLE))
+ {
+ int tmout = 0;
+ if (!corb_read
+ (mixer, cad, wid, 0, GET_PIN_SENSE, 0, &a, &b))
+ a = 0x7fffffff; /* No jack present */
+
+ if (a & (1UL << 31)) /* Jack present */
+ if (widget->pincaps & PINCAP_TRIGGERR_RQRD) /* Trigger required */
+ {
+ corb_write (mixer, cad, wid, 0, TRIGGER_PIN_SENSE, 0);
+
+ do
+ {
+ oss_udelay (10);
+ if (!corb_read
+ (mixer, cad, wid, 0, GET_PIN_SENSE, 0, &a,
+ &b))
+ break;
+ }
+ while (tmout++ < 10000
+ && ((a & 0x7fffffff) == 0x7fffffff));
+ }
+
+ if (!corb_read
+ (mixer, cad, wid, 0, GET_PIN_SENSE, 0, &a, &b))
+ continue;
+ }
+ else
+ continue;
+
+ /* Precence detect */
+ if (widget->pincaps & PINCAP_JACKSENSE_CAPABLE)
+ {
+#if 0
+ if (a & (1UL << 31))
+ cmn_err (CE_WARN, "%s jack is plugged in\n",
+ widget->name);
+ else
+ cmn_err (CE_WARN, "%s NOT plugged in\n", widget->name);
+#endif
+ if (!(a & (1UL << 31)))
+ plugged_in = 0;
+ }
+
+ widget->plugged_in = plugged_in;
+ if (plugged_in)
+ codec->num_jacks_plugged++;
+
+ /* Impedance sensing */
+ widget->impsense = a & 0x7fffffff;
+ if (plugged_in && (widget->pincaps & PINCAP_IMPSENSE_CAPABLE))
+ if (hdaudio_jacksense > 0)
+ if (widget->impsense != 0x7fffffff) /* Sense operation finished */
+ {
+ /* cmn_err (CE_WARN, "%s sense %08x (%d)\n", widget->name, a, a & 0x7fffffff); */
+
+ if (widget->impsense >= 1000000) /* High impedance (line-in) pin */
+ {
+ /* cmn_err(CE_CONT, " --> Line level input\n"); */
+ widget->pin_type = widget->sensed_pin = PIN_IN;
+ }
+ else if (widget->impsense <= 10000) /* Low impedance (speaker/hp out) */
+ {
+ /* cmn_err(CE_CONT, " --> Output pin\n"); */
+ widget->pin_type = widget->sensed_pin = PIN_OUT;
+ }
+ else /* Something between low and high (mic?) */
+ {
+ /* cmn_err(CE_CONT, " --> Mic level input\n"); */
+ widget->pin_type = widget->sensed_pin = PIN_MIC;
+ }
+ }
+ }
+ }
+
+/*
+ * Set all pins to correct mode
+ */
+
+ for (cad = 0; cad < mixer->ncodecs; cad++)
+ if (mixer->codecs[cad] != &NULL_codec)
+ for (wid = 1; wid < mixer->codecs[cad]->nwidgets; wid++)
+ {
+ if (mixer->codecs[cad]->widgets[wid].wid_type == NT_PIN) /* Pin complex */
+ {
+ widget_t *widget = &mixer->codecs[cad]->widgets[wid];
+
+ if (widget->pin_type == PIN_IN
+ || widget->pin_type == PIN_UNKNOWN)
+ { /* Input PIN */
+ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x20);
+ }
+ if (widget->pin_type == PIN_MIC)
+ { /* Input PIN (mic) */
+ /* TODO: Handle mic amp */
+ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x24);
+ }
+ else
+ { /* Output PIN */
+ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0xc0);
+ }
+
+ if (widget->pincaps & PINCAP_EAPD)
+ {
+ unsigned int eapd, dummy;
+ DDB (cmn_err
+ (CE_CONT, "Pin widget %d is EAPD capable.\n",
+ widget->wid));
+ if (corb_read
+ (mixer, cad, wid, 0, GET_EAPD, 0, &eapd, &dummy))
+ {
+ eapd |= 0x02; /* EAPD enable */
+ corb_write (mixer, cad, wid, 0, SET_EAPD, eapd);
+ }
+ }
+ }
+ }
+
+ return 1;
+}
+
+static int
+hdaudio_mix_init (int dev)
+{
+ hdaudio_mixer_t *mixer = mixer_devs[dev]->devc;
+ char tmp[32];
+ int err;
+ int working_codecs=0;
+ int cad;
+
+/*
+ * First pass. Count the number of active codecs.
+ */
+
+ for (cad = 0; cad < mixer->ncodecs; cad++)
+ if (mixer->codecs[cad] != &NULL_codec)
+ {
+ codec_t *codec = mixer->codecs[cad];
+
+ if (codec == &NULL_codec || codec->nwidgets == 0) /* Codec not active */
+ continue;
+
+ /*
+ * Enable the new generic mixer driver
+ */
+ if (codec->mixer_init == NULL)
+ {
+ codec->mixer_init = hdaudio_generic_mixer_init;
+ }
+
+ if (codec->active && codec->mixer_init != NULL_mixer_init)
+ working_codecs++;
+ }
+
+ /*
+ * Second pass. Initialize the mixer interfaces for all active codecs.
+ */
+
+ for (cad = 0; cad < mixer->ncodecs; cad++)
+ if (mixer->codecs[cad] != &NULL_codec)
+ {
+ codec_t *codec = mixer->codecs[cad];
+ int group = 0;
+
+ if (codec == &NULL_codec || codec->nwidgets == 0) /* Codec not active */
+ continue;
+
+ if (working_codecs > 1)
+ {
+ sprintf (tmp, "codec%d", cad + 1);
+ if ((group = mixer_ext_create_group (dev, 0, tmp)) < 0)
+ {
+ return group;
+ }
+ }
+
+ if (codec->active && codec->mixer_init != NULL_mixer_init)
+ {
+ if ((err = codec->mixer_init (dev, mixer, cad, group)) < 0)
+ {
+ if (err != OSS_EAGAIN)
+ return err;
+ /*
+ * We got EAGAIN whic means that we should fall
+ * to the old generic mixer.
+ */
+ if ((err =
+ hdaudio_generic_mixer_init (dev, mixer, cad, group)) < 0)
+ {
+ return err;
+ }
+ }
+ else
+ continue;
+ }
+ }
+
+ if (mixer->client_mixer_init != 0)
+ mixer->client_mixer_init (dev);
+
+ return 0;
+}
+
+static void
+copy_endpoints(hdaudio_mixer_t * mixer, codec_t *codec, int pass)
+{
+ int i;
+
+/*
+ * Install output endpoints from the codec to the global endpoint table.
+ */
+
+ for (i = 0; i < codec->num_outendpoints; i++)
+ {
+ hdaudio_endpointinfo_t *ep = &codec->outendpoints[i];
+ ep->skip = mixer->codecs[ep->cad]->widgets[ep->base_wid].skip;
+ }
+
+ for (i=0;i<codec->num_outendpoints;i++)
+ {
+ int ix = (codec->multich_map >> (i * 4)) & 0x0f;
+ hdaudio_endpointinfo_t *ep = &codec->outendpoints[ix];
+
+ ep->skip = mixer->codecs[ep->cad]->widgets[ep->base_wid].skip;
+
+ if (ep->skip || ep->already_used)
+ continue;
+
+ switch (pass)
+ {
+ case 0: /* Pick analog endpoints */
+ if (ep->is_digital)
+ continue;
+ break;
+
+ case 1: /* Pick digital endpoints */
+ if (!ep->is_digital)
+ continue;
+ break;
+ }
+
+ if (mixer->copied_outendpoints >= HDA_MAX_OUTSTREAMS)
+ {
+ cmn_err (CE_WARN,
+ "Too many output endpoints (%d)\n",
+ mixer->copied_outendpoints);
+ continue;
+ }
+
+ memcpy(&mixer->outendpoints[mixer->copied_outendpoints++], ep, sizeof(*ep));
+ ep->already_used=1;
+ }
+ mixer->num_outendpoints = mixer->copied_outendpoints;
+
+/*
+ * Install input endpoints from the codec to the global endpoint table.
+ */
+
+ for (i = 0; i < codec->num_inendpoints; i++)
+ {
+ hdaudio_endpointinfo_t *ep = &codec->inendpoints[i];
+ ep->skip = mixer->codecs[ep->cad]->widgets[ep->base_wid].skip;
+ }
+
+ for (i=0;i<codec->num_inendpoints;i++)
+ {
+ hdaudio_endpointinfo_t *ep = &codec->inendpoints[i];
+
+ ep->skip = mixer->codecs[ep->cad]->widgets[ep->base_wid].skip;
+
+ if (ep->skip || ep->already_used)
+ continue;
+
+ switch (pass)
+ {
+ case 0: /* Pick analog endpoints */
+ if (ep->is_digital)
+ continue;
+ break;
+
+ case 1: /* Pick digital endpoints */
+ if (!ep->is_digital)
+ continue;
+ break;
+ }
+
+ if (mixer->copied_inendpoints >= HDA_MAX_INSTREAMS)
+ {
+ cmn_err (CE_WARN,
+ "Too many output endpoints (%d)\n",
+ mixer->copied_inendpoints);
+ continue;
+ }
+
+ memcpy(&mixer->inendpoints[mixer->copied_inendpoints++], ep, sizeof(*ep));
+ ep->already_used=1;
+ }
+ mixer->num_inendpoints = mixer->copied_inendpoints;
+}
+
+ /*ARGSUSED*/
+ hdaudio_mixer_t *
+hdaudio_mixer_create (char *name, void *devc,
+ oss_device_t * osdev,
+ hdmixer_write_t writefunc,
+ hdmixer_read_t readfunc, unsigned int codecmask,
+ unsigned int vendor_id, unsigned int subvendor_id)
+{
+ hdaudio_mixer_t *mixer;
+ int i, func;
+ int ncodecs = 0;
+ char tmp[128];
+ int mixer_dev;
+
+ char *hw_info = osdev->hw_info; /* For printing hardware information */
+
+ if ((mixer = PMALLOC (osdev, sizeof (*mixer))) == NULL)
+ {
+ cmn_err (CE_WARN, "hdaudio_mixer_create: Out of memory\n");
+ return NULL;
+ }
+
+ memset (mixer, 0, sizeof (*mixer));
+
+ mixer->devc = devc;
+ mixer->osdev = osdev;
+ mixer->mixer_dev = 0;
+ strncpy (mixer->name, name, sizeof (mixer->name));
+ mixer->name[sizeof (mixer->name) - 1] = 0;
+
+ for (i = 0; i < MAX_CODECS; i++)
+ mixer->codecs[i] = &NULL_codec;
+
+ mixer->read = readfunc;
+ mixer->write = writefunc;
+ mixer->codecmask = codecmask;
+
+ sprintf (hw_info, "HD Audio controller %s\n"
+ "Vendor ID 0x%08x\n"
+ "Subvendor ID 0x%08x\n", name, vendor_id, subvendor_id);
+ hw_info += strlen (hw_info);
+
+ if (hdaudio_snoopy)
+ {
+ sprintf(hw_info, "**** Warning: Diagnostic mode enabled (hdaudio_snoopy) ****\n");
+ hw_info += strlen (hw_info);
+ }
+
+/*
+ * Search first all audio function groups for all codecs and then
+ * handle modem function groups.
+ */
+ for (func=1;func<=2;func++)
+ for (i = 0; i < 16; i++)
+ if (mixer->codecmask & (1 << i))
+ {
+ if (attach_codec (mixer, i, hw_info, subvendor_id, func) >= 0)
+ ncodecs++;
+ hw_info += strlen (hw_info);
+ }
+
+ if (ncodecs == 0)
+ {
+ cmn_err (CE_WARN, "No hdaudio codecs were detected\n");
+ return NULL;
+ }
+
+/*
+ * The attach_codec routine copied all analog endpoints to the global endpoint
+ * table. Now pick possible digital endpoints from the active codecs.
+ */
+
+ for (i = 0; i < 16; i++)
+ if (mixer->codecmask & (1 << i))
+ if (mixer->codecs[i]->active)
+ {
+ copy_endpoints(mixer, mixer->codecs[i], 1); /* Copy digital endpoints from codec to mixer */
+ }
+
+ if (!mixer->remap_avail)
+ check_names (mixer);
+
+ propagate_names (mixer);
+ perform_pin_sense (mixer);
+
+ if (mixer->chip_name == NULL)
+ mixer->chip_name = "None";
+ DDB (cmn_err (CE_CONT, "Mixer: %s %s\n", mixer->name, mixer->chip_name));
+ //sprintf (tmp, "%s %s", mixer->name, mixer->chip_name);
+ sprintf (tmp, "High Definition Audio %s", mixer->chip_name);
+
+ if ((mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION,
+ osdev,
+ osdev,
+ tmp,
+ &hda_mixer_driver,
+ sizeof (mixer_driver_t), mixer)) < 0)
+ {
+ return NULL;
+ }
+
+ mixer_devs[mixer_dev]->hw_devc = devc;
+ mixer_devs[mixer_dev]->priority = 10; /* Known motherboard device */
+ mixer->mixer_dev = mixer_dev;
+ mixer_ext_set_init_fn (mixer->mixer_dev, hdaudio_mix_init,
+ mixer->ncontrols * 4);
+ touch_mixer (mixer->mixer_dev);
+
+ return mixer;
+}
+
+/*ARGSUSED*/
+static int
+find_playvol_widget (hdaudio_mixer_t * mixer, int cad, int wid)
+{
+ int this_wid = wid;
+
+ return this_wid;
+}
+
+/*ARGSUSED*/
+static int
+find_recvol_widget (hdaudio_mixer_t * mixer, int cad, int wid)
+{
+ int this_wid = wid;
+
+ return this_wid;
+}
+
+/*ARGSUSED*/
+static int
+find_recsrc_widget (hdaudio_mixer_t * mixer, int cad, int wid)
+{
+ int this_wid = wid;
+
+ return this_wid;
+}
+
+static int
+attach_node (hdaudio_mixer_t * mixer, int cad, int wid, int parent)
+{
+ static const char *widget_types[16] = {
+ "Audio output",
+ "Audio input",
+ "Audio mixer",
+ "Audio selector",
+ "Pin complex",
+ "Power widget",
+ "Volume knob",
+ "Beep generator",
+ "Reserved8",
+ "Reserved9",
+ "ReservedA",
+ "ReservedB",
+ "ReservedC",
+ "ReservedD",
+ "ReservedE",
+ "Vendor defined audio"
+ };
+
+ static const char *widget_id[16] = {
+ "pcm",
+ "rec",
+ "mix",
+ "select",
+ "jack",
+ "power",
+ "vol",
+ "beep",
+ "unkn",
+ "unkn",
+ "unkn",
+ "unkn",
+ "unkn",
+ "unkn",
+ "unkn",
+ "vendor"
+ };
+
+ static const int bit_sizes[] = {
+ 8,
+ 16,
+ 20,
+ 24,
+ 32
+ };
+
+ static const unsigned int bit_fmts[] = {
+ AFMT_U8,
+ AFMT_S16_LE,
+ AFMT_S32_LE,
+ AFMT_S32_LE,
+ AFMT_S32_LE
+ };
+
+ static const int srates[] = {
+ 8000,
+ 11025,
+ 16000,
+ 22050,
+ 32000,
+ 44100,
+ 48000,
+ 88200,
+ 96000,
+ 176400,
+ 192000,
+ 384000
+ };
+
+ unsigned int widget_caps, b, pstate, group_type_in;
+ unsigned int inamp_caps, outamp_caps;
+ int group_type, wid_type;
+ int i;
+
+ codec_t *codec = mixer->codecs[cad];
+ widget_t *widget;
+
+ if (codec == &NULL_codec)
+ return 0;
+
+ if (!corb_read
+ (mixer, cad, wid, 0, GET_PARAMETER, HDA_GROUP_TYPE, &group_type_in, &b))
+ group_type_in = 0;
+
+ if (wid >= MAX_WIDGETS)
+ {
+ cmn_err (CE_WARN, "Too many widgets for codec %d (%d/%d)\n", cad, wid, MAX_WIDGETS);
+ return 0;
+ }
+
+ mixer->ncontrols++;
+
+ codec->nwidgets = wid + 1;
+ widget = &codec->widgets[wid];
+
+ widget->cad = cad;
+ widget->wid = wid;
+
+ widget->rgbcolor = 0;
+
+ group_type = group_type_in & 0xff;
+ widget->group_type = group_type_in;
+
+ DDB (cmn_err (CE_CONT, "Node %d, parent %d type %d, unsol capa %d\n",
+ wid, parent, group_type, !!(group_type_in & 0x100)));
+
+ if (!corb_read
+ (mixer, cad, wid, 0, GET_PARAMETER, HDA_WIDGET_CAPS, &widget_caps, &b))
+ {
+ cmn_err (CE_WARN, "GET_PARAMETER HDA_WIDGET_CAPS failed\n");
+ return 0;
+ }
+
+ if (widget_caps & WCAP_AMP_CAP_OVERRIDE) /* Amp param override? */
+ {
+ if (!corb_read (mixer, widget->cad, widget->wid, 0,
+ GET_PARAMETER, HDA_OUTPUTAMP_CAPS, &outamp_caps, &b))
+ {
+ cmn_err (CE_WARN, "GET_PARAMETER HDA_OUTPUTAMP_CAPS failed\n");
+ return OSS_EIO;
+ }
+ widget->outamp_caps = outamp_caps;
+
+ if (!corb_read (mixer, widget->cad, widget->wid, 0,
+ GET_PARAMETER, HDA_INPUTAMP_CAPS, &inamp_caps, &b))
+ {
+ cmn_err (CE_WARN, "GET_PARAMETER HDA_INPUTAMP_CAPS failed\n");
+ return OSS_EIO;
+ }
+ widget->inamp_caps = inamp_caps;
+ }
+ else
+ {
+ widget->outamp_caps = outamp_caps = codec->default_outamp_caps;
+ widget->inamp_caps = inamp_caps = codec->default_inamp_caps;
+ }
+
+ if (!corb_read (mixer, cad, wid, 0, GET_POWER_STATE, 0, &pstate, &b))
+ return 0;
+
+ /* power up each of the widgets if there is a Power Capability (1<<10) */
+ if (widget_caps & WCAP_POWER_CTL)
+ corb_write (mixer, cad, wid, 0, SET_POWER_STATE, 0);
+
+ widget->widget_caps = widget_caps;
+
+ wid_type = (widget_caps >> 20) & 0x0f;
+ DDB (cmn_err
+ (CE_CONT, "\tWidget type %d (%s)(%s)\n", wid_type,
+ widget_types[wid_type], widget_id[wid_type]));
+ DDB (cmn_err (CE_CONT, "\tPower State %d\n", pstate));
+
+ if (widget_caps & WCAP_CONN_LIST)
+ {
+ unsigned int clen;
+ /* Handle connection list */
+
+ if (!corb_read
+ (mixer, cad, wid, 0, GET_PARAMETER, HDA_CONNLIST_LEN, &clen, &b))
+ {
+ cmn_err (CE_WARN, "GET_PARAMETER HDA_CONNLIST_LEN failed\n");
+ return 0;
+ }
+
+ if (clen & 0x80)
+ {
+ cmn_err (CE_WARN, "Long form connection list not supported\n");
+ return 0;
+ }
+
+ if (clen > 0)
+ {
+ if (clen > MAX_CONN)
+ {
+ cmn_err (CE_WARN, "Too many connections\n");
+ return 0;
+ }
+
+ DDB (cmn_err (CE_CONT, "\tConn list (%d): ", clen));
+
+ for (i = 0; i < clen; i += 4)
+ {
+ int j;
+ unsigned int a;
+
+ if (!corb_read
+ (mixer, cad, wid, 0, GET_CONNECTION_LIST_ENTRY, i, &a, &b))
+ {
+ cmn_err (CE_WARN, "GET_CONNECTION_LIST_ENTRY failed\n");
+ return 0;
+ }
+
+ for (j = 0; j < 4 && (i + j) < clen; j++)
+ {
+ int v, is_range = 0;
+
+ if (widget->nconn >= MAX_CONN)
+ {
+ cmn_err (CE_WARN,
+ "Too many connections for widget %d (%d)\n",
+ widget->wid, widget->nconn);
+ break;
+ }
+
+ v = (a >> (j * 8)) & 0xff;
+ DDB (cmn_err (CE_CONT, "%d ", v));
+
+ if (v & 0x80)
+ {
+ is_range = 1;
+ v &= ~0x80;
+ }
+
+ if (v < 0 || v >= MAX_WIDGETS)
+ {
+ cmn_err (CE_NOTE,
+ "Connection %d for widget %d is out of range (%d) - Skipped\n",
+ j, widget->wid, v);
+ continue;
+ }
+
+ if (is_range) /* Range: prev...v */
+ {
+ int x;
+ codec->widgets[v].references[codec->widgets[v].
+ refcount++] = wid;
+
+ if (widget->nconn < 1)
+ {
+ cmn_err (CE_CONT,
+ "Bad range connection for widget %d\n",
+ widget->wid);
+ continue;
+ }
+
+ for (x = widget->connections[widget->nconn - 1] + 1;
+ x <= v; x++)
+ {
+ if (widget->nconn >= MAX_CONN)
+ {
+ cmn_err (CE_WARN,
+ "Too many connections(B) for widget %d (%d)\n",
+ widget->wid, widget->nconn);
+ break;
+ }
+
+ widget->connections[widget->nconn++] = x;
+ codec->widgets[x].references[codec->widgets[x].
+ refcount++] = wid;
+ }
+ }
+ else
+ {
+ widget->connections[widget->nconn++] = v;
+ codec->widgets[v].references[codec->widgets[v].
+ refcount++] = wid;
+ }
+ }
+ }
+
+ DDB (cmn_err (CE_CONT, "\n"));
+ }
+ }
+
+ widget->wid_type = wid_type;
+
+ strcpy (widget->name, widget_id[wid_type]);
+
+ if (group_type == 0) /* Not group but a widget */
+ switch (wid_type)
+ {
+ case NT_DAC: /* Audio output */
+ case NT_ADC: /* Audio input */
+ {
+ unsigned int sizes;
+ int j;
+ hdaudio_endpointinfo_t *endpoint;
+
+ if (wid_type == 0)
+ { /* Output endpoint */
+ if (mixer->num_outendpoints >= HDA_MAX_OUTSTREAMS)
+ {
+ cmn_err (CE_WARN, "Too many output endpoints\n");
+ return 0;
+ }
+
+ endpoint = &codec->outendpoints[codec->num_outendpoints++];
+
+ endpoint->stream_number = endpoint->default_stream_number =
+ ++mixer->num_outendpoints;
+ endpoint->ix = codec->num_outendpoints - 1;
+ endpoint->iddle_stream = 0;
+ }
+ else
+ { /* Input endpoint */
+ if (mixer->num_inendpoints >= HDA_MAX_INSTREAMS)
+ {
+ cmn_err (CE_WARN, "Too many input endpoints\n");
+ return 0;
+ }
+
+ endpoint = &codec->inendpoints[codec->num_inendpoints++];
+
+ endpoint->stream_number = endpoint->default_stream_number =
+ ++mixer->num_inendpoints;
+ endpoint->ix = codec->num_inendpoints - 1;
+ endpoint->iddle_stream = 0;
+ }
+
+ endpoint->cad = cad;
+ endpoint->base_wid = wid;
+ endpoint->recsrc_wid = wid;
+ endpoint->volume_wid = wid;
+ endpoint->nrates=0;
+ endpoint->name = widget->name;
+
+ widget->endpoint = endpoint;
+
+ /*
+ * Find the widgets that manage rec/play volumes and recording
+ * source selection.
+ */
+ switch (wid_type)
+ {
+ case NT_DAC:
+ endpoint->volume_wid = find_playvol_widget (mixer, cad, wid);
+ break;
+
+ case NT_ADC:
+ endpoint->volume_wid = find_recvol_widget (mixer, cad, wid);
+ endpoint->recsrc_wid = find_recsrc_widget (mixer, cad, wid);
+ break;
+ }
+
+ if (widget->widget_caps & WCAP_STEREO)
+ endpoint->channels = 2;
+ else
+ endpoint->channels = 1;
+
+ sizes = codec->sizes;
+
+ if (widget->widget_caps & WCAP_DIGITAL) /* Digital */
+ {
+ endpoint->is_digital = 1;
+ if (wid_type == NT_ADC)
+ strcpy (widget->name, "spdifin");
+ else
+ {
+ strcpy (widget->name, "spdifout");
+ corb_write (mixer, cad, wid, 0, SET_SPDIF_CONTROL1, 1); /* Digital output enable */
+ endpoint->iddle_stream = FRONT_STREAM;
+ }
+
+ endpoint->fmt_mask |= AFMT_AC3;
+ if (sizes & (1 << 20)) /* 32 bits */
+ {
+ endpoint->fmt_mask |= AFMT_SPDIF_RAW;
+ }
+ }
+ else
+ {
+ endpoint->is_digital = 0;
+ }
+
+ if (widget->widget_caps & WCAP_FORMAT_OVERRIDE) /* Override */
+ {
+ if (!corb_read (mixer, cad, wid, 0,
+ GET_PARAMETER, HDA_PCM_SIZES, &sizes, &b))
+ {
+ cmn_err (CE_WARN, "GET_PARAMETER HDA_PCM_SIZES failed\n");
+ return 0;
+ }
+ }
+
+ widget->sizes = sizes;
+ endpoint->sizemask = sizes;
+ if (sizes == 0)
+ {
+ corb_read (mixer, cad, codec->afg, 0, GET_PARAMETER,
+ HDA_PCM_SIZES, &sizes, &b);
+ widget->sizes = sizes;
+ endpoint->sizemask = sizes;
+ }
+
+ DDB (cmn_err (CE_CONT, "\tPCM Size/Rate %08x\n", sizes));
+
+ for (j = 0; j < 5; j++)
+ if (sizes & (1 << (j + 16)))
+ {
+ DDB (cmn_err
+ (CE_CONT, "\t\tSupports %d bits\n", bit_sizes[j]));
+ endpoint->fmt_mask |= bit_fmts[j];
+ }
+
+ for (j = 0; j < 12; j++)
+ if (sizes & (1 << j))
+ {
+ DDB (cmn_err (CE_CONT, "\t\tSupports %d Hz\n", srates[j]));
+ if (endpoint->nrates < 20)
+ {
+ endpoint->rates[endpoint->nrates++] = srates[j];
+ }
+ }
+
+ if ((widget->widget_caps & WCAP_DIGITAL) && wid_type == NT_DAC) /* Digital output */
+ {
+ /*
+ * Select front output as the default stream number. In
+ * this way copy of the analog front signal will automatically
+ * be delivered to the S/PDIF outpput when the S/PDIF device
+ * is not being used for some other purpose.
+ */
+ corb_write (mixer, cad, wid, 0, SET_CONVERTER,
+ FRONT_STREAM << 4);
+ }
+ else
+ {
+ /* Select the iddle stream (0) for analog outputs */
+ corb_write (mixer, cad, wid, 0, SET_CONVERTER,
+ IDDLE_STREAM << 4);
+ }
+ /* Select 48 kHz/16 bits/stereo */
+ corb_write (mixer, cad, wid, 0, SET_CONVERTER_FORMAT, 0x0009);
+
+ }
+ break;
+
+ case NT_KNOB: /* Volume knob */
+ /* Clear the direct control bit */
+ corb_write (mixer, cad, wid, 0, SET_VOLUME_KNOB_CONTROL, 0x00);
+ break;
+
+ case NT_PIN: /* Pin complex */
+ {
+ unsigned int conf;
+ unsigned int pincaps;
+
+ if (!corb_read
+ (mixer, cad, wid, 0, GET_CONFIG_DEFAULT, 0, &conf, &b))
+ return 0;
+ if (!corb_read (mixer, cad, wid, 0,
+ GET_PARAMETER, HDA_PIN_CAPS, &pincaps, &b))
+ {
+ cmn_err (CE_WARN, "GET_PARAMETER HDA_PIN_CAPS failed\n");
+ return 0;
+ }
+
+ widget->pincaps = pincaps;
+#if 0
+ if (widget->widget_caps & WCAP_UNSOL_CAPABLE)
+ corb_write (mixer, cad, wid, 0, SET_UNSOLICITED, 0x80 | wid);
+#endif
+
+ if (conf != 0)
+ {
+ int default_device = (conf >> 20) & 0x0f;
+ int default_loc = (conf >> 24) & 0x3f;
+ int conn = (conf >> 30) & 0x03;
+
+ int color;
+ int no_color=0;
+ char *name = NULL, *loc = "", *color_name = NULL;
+
+ color = (conf >> 12) & 0x0f;
+
+ if (pincaps & (1 << 6))
+ cmn_err (CE_WARN, "Balanced I/O not supported\n");
+ if (!(pincaps & (1 << 5))) /* No input */
+ widget->pin_type = PIN_OUT;
+ if (!(pincaps & (1 << 4)))
+ widget->pin_type = PIN_IN;
+
+ DDB (cmn_err (CE_CONT, "\tConfig default %08x\n", conf));
+
+ if ((default_loc & 0x0f) == 0x1) /* Rear panel - default */
+ loc = "";
+ if ((default_loc & 0x0f) == 0x2) /* Front panel */
+ loc = "fp-";
+ if ((default_loc & 0xf0) == 0x10) /* Internal func - eg cd/tad/spk */
+ loc = "int-";
+
+ if (conn == 1 && !(hdaudio_noskip & 1)) /* Pin not connected to anything */
+ {
+ widget->skip = 1;
+ widget->skip_output = 1;
+ }
+
+ switch (default_device)
+ {
+ case 0x0:
+ name = "lineout";
+ widget->pin_type = PIN_OUT;
+ break;
+ case 0x1:
+ name = "speaker";
+ no_color=1;
+ widget->pin_type = PIN_OUT;
+ break;
+ case 0x2:
+ name = "headphone";
+ widget->pin_type = PIN_OUT;
+ break;
+ case 0x3:
+ name = "cd";
+ no_color=1;
+ widget->pin_type = PIN_IN;
+ break;
+ case 0x4:
+ name = "spdifout";
+ no_color=1;
+ widget->pin_type = PIN_OUT;
+ break;
+ case 0x5:
+ name = "digout";
+ no_color=1;
+ widget->pin_type = PIN_OUT;
+ break;
+ case 0x6:
+ name = "modem";
+ no_color=1;
+ break;
+ case 0x7:
+ name = "phone";
+ no_color=1;
+ break;
+ case 0x8:
+ name = "linein";
+ widget->pin_type = PIN_IN;
+ break;
+ case 0x9:
+ name = "aux";
+ break;
+ case 0xa:
+ name = "mic";
+ widget->pin_type = PIN_MIC;
+ break;
+ case 0xb:
+ name = "telephony";
+ no_color=1;
+ break;
+ case 0xc:
+ name = "spdifin";
+ no_color=1;
+ break;
+ case 0xd:
+ name = "digin";
+ no_color=1;
+ break;
+ case 0xe:
+ name = "reserved";
+ no_color=1;
+ break;
+ case 0xf: /* Unused pin widget */
+ if (hdaudio_noskip & 2) break;
+ widget->skip = 1;
+ widget->skip_output = 1;
+ break;
+ }
+
+/* process only colored jacks and skip fixed function jacks */
+ switch (color)
+ {
+ case 0x1:
+ color_name = "black";
+ widget->rgbcolor = OSS_RGB_BLACK;
+ break;
+ case 0x2:
+ color_name = "gray";
+ widget->rgbcolor = OSS_RGB_GRAY;
+ break;
+ case 0x3:
+ color_name = "blue";
+ widget->rgbcolor = OSS_RGB_BLUE;
+ break;
+ case 0x4:
+ color_name = "green";
+ widget->rgbcolor = OSS_RGB_GREEN;
+ break;
+ case 0x5:
+ color_name = "red";
+ widget->rgbcolor = OSS_RGB_RED;
+ break;
+ case 0x6:
+ color_name = "orange";
+ widget->rgbcolor = OSS_RGB_ORANGE;
+ break;
+ case 0x7:
+ color_name = "yellow";
+ widget->rgbcolor = OSS_RGB_YELLOW;
+ break;
+ case 0x8:
+ color_name = "purple";
+ widget->rgbcolor = OSS_RGB_PURPLE;
+ break;
+ case 0x9:
+ color_name = "pink";
+ widget->rgbcolor = OSS_RGB_PINK;
+ break;
+ case 0xe:
+ color_name = "white";
+ widget->rgbcolor = OSS_RGB_WHITE;
+ break;
+
+ default:
+ if (name != NULL)
+ color_name = name;
+ else
+ color_name = "internal";
+ }
+
+ if (no_color)
+ widget->rgbcolor = 0;
+
+ if (default_device == 0xf) /* Not present */
+ {
+ widget->rgbcolor=0;
+ color_name = "internal";
+ }
+
+ sprintf (widget->color, "%s%s", loc, color_name);
+/*
+ * By Hannu 20080111
+ * Use jack color as the widget name if no name was defined or if the default
+ * function is lineout. This fixes the problem of several jacks being named
+ * as lineout.
+ */
+ if (name == NULL || default_device == 0x00)
+ name = color_name;
+ sprintf (widget->name, "%s%s", loc, name);
+ }
+ }
+ break;
+ }
+
+#if 0
+ if (!corb_read
+ (mixer, cad, wid, 0, GET_PARAMETER, HDA_NODE_COUNT, &node_count, &b))
+ {
+ cmn_err (CE_WARN, "GET_PARAMETER HDA_NODE_COUNT1 failed\n");
+ return 0;
+ }
+
+ first_node = (node_count >> 16) & 0xff;
+ num_nodes = node_count & 0xff;
+
+ DDB (cmn_err
+ (CE_CONT, "\tFirst node %d, num nodes %d\n", first_node, num_nodes));
+
+ if (first_node > 0)
+ for (i = first_node; i < first_node + num_nodes; i++)
+ if (!attach_node (mixer, cad, i, wid))
+ return 0;
+#endif
+/*
+ * Handle hardcodded widget names
+ */
+
+ if (codec->remap != NULL)
+ {
+ int w;
+
+ mixer->remap_avail=1;
+
+ for (w = 0; codec->remap[w] != NULL; w++)
+ if (w == wid)
+ {
+ char *s = codec->remap[w];
+
+ if (*s != 0)
+ {
+ strcpy (widget->name, s);
+ if (*widget->color == 0)
+ {
+ strcpy (widget->color, widget->name);
+ }
+ }
+ break;
+ }
+ }
+
+ return 1;
+}
+
+/*ARGSUSED*/
+static int
+attach_group (hdaudio_mixer_t * mixer, int cad, int wid, int parent, int group_type)
+{
+ unsigned int a, b, gt;
+ int i, first_node, num_nodes;
+ codec_t *codec = mixer->codecs[cad];
+
+ if (codec == &NULL_codec)
+ return 0;
+
+ if (!corb_read (mixer, cad, wid, 0,
+ GET_PARAMETER, HDA_OUTPUTAMP_CAPS,
+ &codec->default_outamp_caps, &b))
+ {
+ cmn_err (CE_WARN, "GET_PARAMETER HDA_OUTPUTAMP_CAPS failed\n");
+ return 0;
+ }
+
+ if (!corb_read (mixer, cad, wid, 0,
+ GET_PARAMETER, HDA_INPUTAMP_CAPS,
+ &codec->default_inamp_caps, &b))
+ {
+ cmn_err (CE_WARN, "GET_PARAMETER HDA_INPUTTAMP_CAPS failed\n");
+ return 0;
+ }
+
+ if (!corb_read (mixer, cad, wid, 0, GET_PARAMETER, HDA_NODE_COUNT, &a, &b))
+ {
+ cmn_err (CE_WARN, "GET_PARAMETER HDA_NODE_COUNT2 failed\n");
+ return 0;
+ }
+
+ first_node = (a >> 16) & 0xff;
+ num_nodes = a & 0xff;
+
+ corb_read (mixer, cad, wid, 0, GET_PARAMETER, HDA_GROUP_TYPE, &gt, &b);
+ DDB (cmn_err (CE_CONT, "\tGroup %d First node %d, num nodes %d\n", wid,
+ first_node, num_nodes));
+/*
+ * Ignore other than audio function groups. Codecs probably allocate
+ * higher widget number for the modem group than the audio group. So in this
+ * way we can have smaller MAX_WIDGETS which in turn conserves memory.
+ */
+ if ((gt & 0xff) != group_type)
+ return 0;
+
+ if (corb_read (mixer, cad, wid, 0, GET_PARAMETER, HDA_PCM_SIZES, &a, &b))
+ {
+ codec->sizes = a;
+ }
+
+ if (first_node > 0)
+ for (i = first_node; i < first_node + num_nodes; i++)
+ if (!attach_node (mixer, cad, i, wid))
+ return 0;
+
+ if (num_nodes >= 1)
+ codec->active=1;
+ return 1;
+}
+
+static void
+polish_widget_list (hdaudio_mixer_t * mixer, int cad)
+{
+ widget_t *widget;
+ codec_t *codec;
+ int wid, conn, loop;
+ int skip = 0;
+ int do_jacksense = 0;
+
+ if (hdaudio_noskip & 4) return;
+
+ if (mixer->codecs[cad] == &NULL_codec)
+ {
+ cmn_err (CE_WARN, "Bad codec %d\n", cad);
+ }
+ codec = mixer->codecs[cad];
+
+ for (wid = 0; wid < codec->nwidgets; wid++)
+ {
+ widget = &codec->widgets[wid];
+ }
+
+/*
+ * Use jack sensing information to remove unconnected I/O pints from the mixer
+ * interface.
+ */
+
+ do_jacksense = 1;
+ if (codec->num_jacks_plugged < 1 || hdaudio_jacksense < 1)
+ do_jacksense = 0;
+
+ if (do_jacksense)
+ for (wid = 0; wid < codec->nwidgets; wid++)
+ {
+ widget = &codec->widgets[wid];
+
+ if (widget->wid_type != NT_PIN) /* Not a pin widget */
+ continue;
+
+ if (!widget->plugged_in)
+ widget->skip = 1;
+ }
+
+ /*
+ * Check all widgets and mark them unusable (skip=1) if all of their input
+ * connections are marked to be skipped.
+ *
+ * This needs to be done number of times so that the skip status propagates
+ * to the end of the longest path.
+ */
+
+ for (loop = 0; loop < codec->nwidgets / 4; loop++) /* nwidgets/4 iterations */
+ for (wid = 0; wid < codec->nwidgets; wid++)
+ {
+ widget = &codec->widgets[wid];
+
+ if (widget->skip || widget->used)
+ continue;
+
+ skip = 1; /* For now */
+ for (conn = 0; conn < widget->nconn; conn++)
+ {
+ if (!codec->widgets[widget->connections[conn]].skip)
+ skip = 0; /* Cannot be skipped */
+ }
+
+ if (skip && widget->nconn > 0)
+ {
+ widget->skip = 1;
+ widget->used = 1;
+ }
+ }
+
+/*
+ * Do the same backwards. Remove widgets that don't have connectivity to any
+ * of the pin widgets.
+ */
+ for (loop = 0; loop < codec->nwidgets / 4; loop++) /* nwidgets/4 iterations */
+ for (wid = 0; wid < codec->nwidgets; wid++)
+ {
+ widget = &codec->widgets[wid];
+
+ if (widget->skip_output || widget->used)
+ continue;
+
+ skip = 1; /* For now */
+ for (conn = 0; conn < widget->refcount; conn++)
+ {
+ if (!codec->widgets[widget->references[conn]].skip_output)
+ skip = 0; /* Cannot be skipped */
+ }
+
+ if (skip && widget->refcount > 0)
+ {
+ widget->skip_output = 1;
+ widget->used = 1;
+ }
+ }
+
+/*
+ * Final pass.
+ */
+
+ for (wid = 0; wid < codec->nwidgets; wid++)
+ {
+ widget = &codec->widgets[wid];
+
+ if (widget->skip_output)
+ {
+ widget->skip = 1;
+ }
+ }
+}
+
+
+/* ARGSUSED */
+static int
+attach_codec (hdaudio_mixer_t * mixer, int cad, char *hw_info,
+ unsigned int pci_subdevice, int group_type)
+{
+ unsigned int a, b, x;
+ int subix, ix, i;
+ int first_node, num_nodes;
+ int has_audio_group = 0;
+ codec_t *codec;
+
+ if (cad >= MAX_CODECS)
+ {
+ cmn_err (CE_WARN, "attach_codec: Too many codecs %d\n", cad);
+ return OSS_EIO;
+ }
+
+ mixer->ncodecs = cad + 1;
+
+ if (mixer->codecs[cad] == &NULL_codec)
+ {
+ if ((codec = PMALLOC (mixer->osdev, sizeof (*codec))) == NULL)
+ {
+ cmn_err (CE_CONT, "Cannot allocate codec descriptor\n");
+ return OSS_ENOMEM;
+ }
+
+ memset (codec, 0, sizeof (*codec));
+
+ mixer->codecs[cad] = codec;
+ }
+ else
+ {
+ codec = mixer->codecs[cad];
+ }
+
+ corb_write (mixer, cad, 0, 0, SET_POWER_STATE, 0); /* Power up everything */
+
+ if (!corb_read (mixer, cad, 0, 0, GET_PARAMETER, HDA_VENDOR, &a, &b))
+ {
+ if (group_type == 1)
+ {
+ sprintf (hw_info, " Codec %2d: Not present\n", cad);
+ cmn_err (CE_NOTE,
+ "attach_codec: Codec #%d is not physically present\n",
+ cad);
+ }
+ return OSS_EIO;
+ }
+ codec->vendor_id = a;
+
+/*
+ * Find out the primary group list
+ */
+
+ if (!corb_read (mixer, cad, 0, 0, GET_PARAMETER, HDA_NODE_COUNT, &x, &b))
+ {
+ cmn_err (CE_WARN, "GET_PARAMETER HDA_NODE_COUNT3 failed\n");
+ return OSS_EIO;
+ }
+
+ codec->first_node = first_node = (x >> 16) & 0xff;
+ num_nodes = x & 0xff;
+
+/*
+ * Check if this one is an audio codec (has an audio function group)
+ */
+ for (i = first_node; i < first_node + num_nodes; i++)
+ {
+ unsigned int gt;
+
+ corb_read (mixer, cad, i, 0, GET_PARAMETER, HDA_GROUP_TYPE, &gt, &b);
+/*
+ * Handle only the function group type requested by the upper layer code.
+ */
+ if ((gt & 0xff) != 1)
+ continue;
+
+ has_audio_group = 1;
+ }
+
+/*
+ * Find codec specific settings
+ */
+ for (ix = 0; codecs[ix].id != 0; ix++)
+ if (codecs[ix].id == a)
+ break;
+
+ DDB (cmn_err
+ (CE_CONT, "HD audio Codec ID: %08x (%s)\n", a, codecs[ix].name));
+
+ if (codecs[ix].id == 0) /* Unknown codec */
+ {
+ if (group_type == 1)
+ sprintf (hw_info, " Codec %2d: Unknown (0x%08x", cad, a);
+ cmn_err (CE_NOTE, "HDA codec 0x%08x not known yet\n", a);
+ /*
+ * Create hexadecimal codec ID
+ */
+ if (has_audio_group && mixer->chip_name == NULL)
+ if ((mixer->chip_name = PMALLOC (mixer->osdev, 32)) != NULL)
+ {
+ sprintf (mixer->chip_name, "0x%08x", a);
+ }
+ }
+ else
+ {
+ if (group_type == 1)
+ sprintf (hw_info, " Codec %2d: %s (0x%08x", cad, codecs[ix].name, a);
+ }
+
+ if (has_audio_group && mixer->chip_name == NULL)
+ {
+ mixer->chip_name = codecs[ix].name;
+
+ }
+
+ if (codecs[ix].remap != NULL)
+ codec->remap = codecs[ix].remap;
+
+ if (codecs[ix].flags != 0)
+ codec->vendor_flags = codecs[ix].flags;
+
+ if (codecs[ix].mixer_init != NULL)
+ codec->mixer_init = codecs[ix].mixer_init;
+
+ codec->multich_map = codecs[ix].multich_map;
+
+ if (corb_read (mixer, cad, 0, 0, GET_PARAMETER, HDA_REVISION, &a, &b))
+ {
+ DDB (cmn_err (CE_CONT, "HDA codec revision %d.%d (%d.%d) (0x%08x)\n",
+ (a >> 20) & 0xf,
+ (a >> 16) & 0xf, (a >> 8) & 0xff, a & 0xff, a));
+ }
+ else
+ DDB (cmn_err (CE_CONT, "Can't get codec revision\n"));
+
+ codec->codec_desc = &codecs[ix];
+
+ DDB (cmn_err (CE_CONT, "**** Codec %d ****\n", cad));
+ DDB (cmn_err
+ (CE_CONT, "First node %d, num nodes %d\n", first_node, num_nodes));
+
+ for (i = first_node; i < first_node + num_nodes; i++)
+ {
+ corb_read (mixer, cad, i, 0, GET_PARAMETER, HDA_GROUP_TYPE, &a, &b);
+ if ((a & 0xff) == group_type) /* Proper function group type */
+ {
+ codec->afg = i;
+
+ if (corb_read (mixer, cad, i, 0, GET_SUBSYSTEM_ID, 0, &a, &b))
+ {
+ DDB (cmn_err (CE_CONT, "Subsystem ID = 0x%08x\n", a));
+
+ if (group_type == 1)
+ {
+ /* Append subvendor ID to hw_info */
+ hw_info += strlen (hw_info);
+ sprintf (hw_info, "/0x%08x", a);
+ }
+
+ codec->subvendor_id = a;
+
+ for (subix = 0; subdevices[subix].id != 0; subix++)
+ if (subdevices[subix].id == a)
+ {
+ if (subdevices[subix].main_id != 0)
+ if (subdevices[subix].main_id != codec->vendor_id)
+ continue;
+
+ if (subdevices[subix].pci_subdevice != 0)
+ if (subdevices[subix].pci_subdevice != pci_subdevice)
+ continue;
+
+
+ DDB (cmn_err
+ (CE_CONT, "Subdevice known as %s\n",
+ subdevices[subix].name));
+ if (group_type == 1)
+ {
+ hw_info += strlen (hw_info);
+ sprintf (hw_info, " %s", subdevices[subix].name);
+ }
+ if (subdevices[subix].remap != NULL)
+ {
+ codec->remap = subdevices[subix].remap;
+ }
+
+ if (subdevices[subix].multich_map != 0)
+ codec->multich_map = subdevices[subix].multich_map;
+ if (subdevices[subix].flags != 0)
+ codec->vendor_flags = subdevices[subix].flags;
+ if (subdevices[subix].mixer_init != NULL)
+ {
+ codec->mixer_init = subdevices[subix].mixer_init;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ hw_info += strlen (hw_info);
+ if (group_type == 1)
+ strcpy (hw_info, ")\n");
+
+ if (codec->multich_map == 0)
+ {
+ codec->multich_map = 0x76543210; /* Sequential order */
+ }
+
+ for (i = first_node; i < first_node + num_nodes; i++)
+ {
+ if (!attach_group (mixer, cad, i, 0, group_type))
+ continue;
+
+ /* power up the AFG! */
+ corb_write (mixer, cad, i, 0, SET_POWER_STATE, 0);
+ }
+
+ /* Initialize and setup manually endpoints for Si3055. */
+ if ((mixer->codecs[cad]->vendor_flags & VF_SI3055_HACK) && (group_type == 2))
+ {
+ hdaudio_si3055_endpoint_init(mixer, cad);
+ }
+
+ if (has_audio_group)
+ {
+ polish_widget_list (mixer, cad);
+ }
+
+ copy_endpoints(mixer, codec, 0); /* Copy analog endpoints from codec to mixer */
+
+ return (has_audio_group) ? 0 : OSS_EIO;
+}
+
+int
+hdaudio_mixer_get_mixdev (hdaudio_mixer_t * mixer)
+{
+ return mixer->mixer_dev;
+}
+
+void
+hdaudio_mixer_set_initfunc (hdaudio_mixer_t * mixer,
+ mixer_create_controls_t func)
+{
+ mixer->client_mixer_init = func;
+}
+
+#define BASE44k (1<<14)
+
+static const struct hdaudio_rate_def _hdaudio_rates[] = {
+ /* 48 kHz based rates */
+ {192000, (3 << 11)}, /* 4x */
+ {96000, (1 << 11)}, /* 2x */
+ {48000, 0}, /* 1x */
+ {24000, (1 << 8)}, /* 1/2x */
+ {16000, (2 << 8)}, /* 1/3x */
+ {12000, (3 << 8)}, /* 1/4x */
+ {9600, (4 << 8)}, /* 1/5x */
+ {8000, (5 << 8)}, /* 1/6x */
+ /* TODO: These rates didn't work for some reason. */
+ /* 44.1 kHz based rates */
+ {176400, BASE44k | (3 << 11)}, /* 4x */
+ {88200, BASE44k | (1 << 11)}, /* 2x */
+ {44100, BASE44k}, /* 1x */
+ {22050, BASE44k | (1 << 8)}, /* 1/2x */
+ {14700, BASE44k | (2 << 8)}, /* 1/3x */
+ {11025, BASE44k | (3 << 8)}, /* 1/4x */
+ {8820, BASE44k | (4 << 8)}, /* 1/5x */
+ {7350, BASE44k | (5 << 8)}, /* 1/6x */
+ {0}
+};
+
+const struct hdaudio_rate_def *hdaudio_rates = _hdaudio_rates;
+
+int
+hdaudio_codec_setup_endpoint (hdaudio_mixer_t * mixer,
+ hdaudio_endpointinfo_t * endpoint,
+ int rate, int channels, int fmt,
+ int stream_number, unsigned int *setupbits)
+{
+
+ unsigned int tmp, spdif, dummy;
+ int i;
+
+ endpoint->auto_muted = 0;
+
+ if (!corb_read
+ (mixer, endpoint->cad, endpoint->base_wid, 0, GET_SPDIF_CONTROL, 0,
+ &spdif, &dummy))
+ spdif = 0;
+
+ spdif &= ~(1 << 5); /* Audio */
+
+ tmp = 0;
+
+ if (fmt == AFMT_AC3)
+ channels = 2;
+
+ tmp |= channels - 1;
+
+ switch (fmt)
+ {
+ case AFMT_U8:
+ break;
+
+ case AFMT_S16_LE:
+ tmp |= 0x00000010;
+ break;
+
+ case AFMT_S32_LE:
+ if (endpoint->sizemask & (1 << 20)) /* 32 bits */
+ tmp |= 0x00000040;
+ else if (endpoint->sizemask & (1 << 19)) /* 24 bits */
+ tmp |= 0x00000030;
+ else if (endpoint->sizemask & (1 << 18)) /* 20 bits */
+ tmp |= 0x00000020;
+ else
+ {
+ cmn_err (CE_WARN, "Bad bit size\n");
+ return OSS_EIO;
+ }
+ break;
+
+ case AFMT_AC3:
+ tmp &= 0xff;
+ tmp |= 0x11; /* 16 bits stereo */
+ spdif |= (1 << 5); /* Data */
+ break;
+
+ case AFMT_SPDIF_RAW:
+ tmp &= 0xff;
+ tmp |= 0x81; /* 32 bits stereo */
+ break;
+
+ default:
+ cmn_err (CE_WARN, "Bad format %x\n", fmt);
+ return OSS_EIO;
+ }
+
+ corb_write (mixer, endpoint->cad, endpoint->base_wid, 0, SET_SPDIF_CONTROL1,
+ spdif & 0xff);
+/*
+ * Finally the sample rate setup
+ */
+
+ for (i = 0; hdaudio_rates[i].rate != 0; i++)
+ if (hdaudio_rates[i].rate == rate)
+ {
+ tmp |= hdaudio_rates[i].mask;
+ break;
+ }
+
+ *setupbits = tmp;
+
+ if (mixer->codecs[endpoint->cad]->vendor_flags & VF_SI3055_HACK)
+ {
+ hdaudio_si3055_set_rate(mixer, endpoint->cad, rate);
+ }
+
+ corb_write (mixer, endpoint->cad, endpoint->base_wid, 0,
+ SET_CONVERTER_FORMAT, tmp);
+ corb_write (mixer, endpoint->cad, endpoint->base_wid, 0, SET_CONVERTER,
+ stream_number << 4);
+
+ if (channels > 2)
+ {
+ /*
+ * Set up the converters for the other stereo pairs
+ */
+#if 1
+ // TODO: Test this
+
+ int n = (channels + 1) / 2;
+
+ for (i = 1; i < n; i++)
+ {
+ hdaudio_endpointinfo_t *ep;
+ ep = &endpoint[i];
+
+ corb_write (mixer, ep->cad, ep->base_wid, 0, SET_CONVERTER_FORMAT,
+ tmp);
+ corb_write (mixer, ep->cad, ep->base_wid, 0, SET_CONVERTER,
+ (stream_number << 4) | (i * 2));
+ }
+#endif
+ }
+
+ if (mixer->codecs[endpoint->cad]->vendor_flags & VF_VAIO_HACK)
+ {
+ /*
+ * STAC9872 specific hack. In Sony VAIO configurations, the DAC widget
+ * used for the headphone jack needs to duplicate the stream playing on
+ * the DAC widget for the speaker when not in multichannel mode.
+ */
+ if (channels <= 2 && endpoint->base_wid == 0x05)
+ {
+ corb_write (mixer, endpoint->cad, 0x02, 0, SET_CONVERTER_FORMAT,
+ tmp);
+ corb_write (mixer, endpoint->cad, 0x02, 0, SET_CONVERTER,
+ stream_number << 4);
+ }
+ }
+
+#if 1
+ if (mixer->codecs[endpoint->cad]->vendor_flags & VF_ALC88X_HACK)
+ {
+ /*
+ * ALC88x specfic hack. These codecs cannot play S/PDIF unless the front
+ * DAC widget is playing the same stream.
+ *
+ * Analog front output (widget 0x14) will be automatically muted.
+ */
+ if (endpoint->is_digital)
+ {
+ unsigned int v, b;
+
+ hdaudio_codec_setup_endpoint (mixer, &mixer->outendpoints[0], rate,
+ channels, fmt, stream_number, &tmp);
+
+ if (fmt == AFMT_AC3)
+ if (corb_read
+ (mixer, endpoint->cad, 0x14, 0, GET_GAIN (1, 0), 0, &v, &b))
+ {
+ endpoint->auto_muted = !(v & 0x80);
+
+ v |= 0x80; /* Mute */
+ corb_write (mixer, endpoint->cad, 0x14, 0,
+ SET_GAIN (1, 0, 1, 1, 0), v);
+ }
+ }
+ }
+#endif
+
+ return 0;
+}
+
+int
+hdaudio_codec_reset_endpoint (hdaudio_mixer_t * mixer,
+ hdaudio_endpointinfo_t * endpoint, int channels)
+{
+
+ int i;
+ unsigned int v, b;
+ int n = (channels + 1) / 2;
+
+ /*
+ * Set all converters to play stream iddle stream (usually 0=silence).
+ */
+
+ corb_write (mixer, endpoint->cad, endpoint->base_wid, 0, SET_CONVERTER,
+ endpoint->iddle_stream << 4);
+
+#if 1
+ // TODO: Test this
+ if (channels > 2)
+ for (i = 1; i < n; i++)
+ {
+ hdaudio_endpointinfo_t *ep;
+
+ ep = &endpoint[i];
+
+ corb_write (mixer, ep->cad, ep->base_wid, 0, SET_CONVERTER,
+ ep->iddle_stream << 4);
+ }
+#endif
+
+ if (mixer->codecs[endpoint->cad]->vendor_flags & VF_VAIO_HACK)
+ /* Also set headphone DAC to play the iddle stream */
+ if (channels <= 2 && endpoint->base_wid == 0x05)
+ {
+ corb_write (mixer, endpoint->cad, 0x02, 0, SET_CONVERTER,
+ endpoint->iddle_stream << 4);
+ }
+
+ if (mixer->codecs[endpoint->cad]->vendor_flags & VF_ALC88X_HACK)
+ if (endpoint->is_digital && endpoint->auto_muted) /* Restore automatic analog mute back to normal */
+ {
+ if (corb_read
+ (mixer, endpoint->cad, 0x14, 0, GET_GAIN (1, 0), 0, &v, &b))
+ {
+ v &= ~0x80; /* Unmute */
+ corb_write (mixer, endpoint->cad, 0x14, 0,
+ SET_GAIN (1, 0, 1, 1, 0), v);
+ endpoint->auto_muted = 0;
+ }
+ }
+
+ return 0;
+}
+
+/*ARGSUSED*/
+void
+hda_codec_unsol (hdaudio_mixer_t * mixer, unsigned int upper,
+ unsigned int lower)
+{
+ DDB (cmn_err (CE_CONT, "Unsol event %08x %08x\n", upper, lower));
+}
+
+int
+hda_codec_getname (hdaudio_mixer_t * mixer, hda_name_t * name)
+{
+ widget_t *widget;
+
+ if (name->cad >= mixer->ncodecs)
+ return OSS_EIO;
+ if (mixer->codecs[name->cad] == &NULL_codec)
+ return OSS_EIO;
+#if 1
+ if (name->wid >= mixer->codecs[name->cad]->nwidgets)
+ return OSS_EIO;
+#endif
+
+ widget = &mixer->codecs[name->cad]->widgets[name->wid];
+ strcpy (name->name, widget->name);
+
+ return 0;
+}
+
+int
+hda_codec_getwidget (hdaudio_mixer_t * mixer, hda_widget_info_t * info)
+{
+ widget_t *widget;
+
+ if (info->cad >= mixer->ncodecs)
+ return OSS_EIO;
+ if (mixer->codecs[info->cad] == &NULL_codec)
+ return OSS_EIO;
+
+ widget = &mixer->codecs[info->cad]->widgets[info->wid];
+ if (info->wid >= mixer->codecs[info->cad]->nwidgets)
+ return OSS_EIO;
+ if (widget == NULL)
+ return OSS_EIO;
+ memcpy (info->info, widget, sizeof (*widget));
+
+ return 0;
+}
+
+int
+hdaudio_codec_audio_ioctl (hdaudio_mixer_t * mixer,
+ hdaudio_endpointinfo_t * endpoint,
+ unsigned int cmd, ioctl_arg arg)
+{
+ //widget_t *base_widget = &mixer->codecs[endpoint->cad]->widgets[endpoint->base_wid];
+ widget_t *recsrc_widget =
+ &mixer->codecs[endpoint->cad]->widgets[endpoint->recsrc_wid];
+ widget_t *volume_widget =
+ &mixer->codecs[endpoint->cad]->widgets[endpoint->volume_wid];
+ char tmp[128], *t;
+ unsigned int linked_wid, a, b;
+ int i, v, left, right;
+ int nsteps;
+
+ if (mixer->codecs[endpoint->cad]->vendor_flags & VF_VAIO_HACK)
+ linked_wid = (endpoint->volume_wid == 0x02) ? 0x05 :
+ ((endpoint->volume_wid == 0x05) ? 0x02 : 0);
+ else
+ linked_wid = 0;
+
+ switch (cmd)
+ {
+ case SNDCTL_DSP_GET_RECSRC_NAMES:
+ *tmp = 0;
+ t = tmp;
+
+ for (i = 0; i < recsrc_widget->nconn; i++)
+ {
+ if (*tmp) /* Not empty */
+ *t++ = ' ';
+ strcpy (t,
+ mixer->codecs[recsrc_widget->cad]->widgets[recsrc_widget->
+ connections[i]].
+ name);
+ t += strlen (t);
+ }
+ return oss_encode_enum ((oss_mixer_enuminfo *) arg, tmp, 0);
+ break;
+
+ case SNDCTL_DSP_GET_RECSRC:
+ if (!corb_read
+ (mixer, recsrc_widget->cad, recsrc_widget->wid, 0, GET_SELECTOR, 0,
+ &a, &b))
+ return OSS_EIO;
+ return *arg = a;
+ break;
+
+ case SNDCTL_DSP_SET_RECSRC:
+ a = *arg;
+ if (a > recsrc_widget->nconn)
+ return OSS_EIO;
+
+ corb_write (mixer, recsrc_widget->cad, recsrc_widget->wid, 0,
+ SET_SELECTOR, a);
+ recsrc_widget->current_selector = a;
+ mixer_devs[mixer->mixer_dev]->modify_counter++;
+ return *arg = a;
+ break;
+
+ case SNDCTL_DSP_GETRECVOL:
+ nsteps = (volume_widget->inamp_caps >> 8) & 0x3f;
+ if (nsteps < 1)
+ nsteps = 1;
+ if (!corb_read
+ (mixer, volume_widget->cad, volume_widget->wid, 0, GET_GAIN (0, 1),
+ 0, &a, &b))
+ return OSS_EIO;
+ if (a & 0x80) /* Muted */
+ left = 0;
+ else
+ left = ((a & 0x7f) * 100) / nsteps;
+ if (!corb_read
+ (mixer, volume_widget->cad, volume_widget->wid, 0, GET_GAIN (0, 0),
+ 0, &a, &b))
+ return OSS_EIO;
+ if (a & 0x80) /* Muted */
+ right = 0;
+ else
+ right = ((a & 0x7f) * 100) / nsteps;
+ v = left | (right << 8);
+ return *arg = v;
+ break;
+
+ case SNDCTL_DSP_SETRECVOL:
+ v = *arg;
+
+ left = v & 0xff;
+ right = (v >> 8) & 0xff;
+
+ if (left < 0)
+ left = 0;
+ if (left > 100)
+ left = 100;
+ if (right < 0)
+ right = 0;
+ if (right > 100)
+ right = 100;
+ v = left | (right << 8);
+
+ nsteps = (volume_widget->inamp_caps >> 8) & 0x3f;
+ if (nsteps < 1)
+ nsteps = 1;
+
+ a = (left * nsteps) / 100;
+ if (left == 0)
+ a |= 0x80; /* Mute */
+ corb_write (mixer, volume_widget->cad, volume_widget->wid, 0,
+ SET_GAIN (0, 1, 1, 0, 0), a);
+ a = (right * nsteps) / 100;
+ if (right == 0)
+ a |= 0x80; /* Mute */
+ corb_write (mixer, volume_widget->cad, volume_widget->wid, 0,
+ SET_GAIN (0, 1, 0, 1, 0), a);
+
+ mixer_devs[mixer->mixer_dev]->modify_counter++;
+ return *arg = v;
+ break;
+
+ case SNDCTL_DSP_GETPLAYVOL:
+ nsteps = (volume_widget->outamp_caps >> 8) & 0x3f;
+ if (nsteps < 1)
+ nsteps = 1;
+ if (!corb_read
+ (mixer, volume_widget->cad, volume_widget->wid, 0, GET_GAIN (1, 1),
+ 0, &a, &b))
+ return OSS_EIO;
+ if (a & 0x80) /* Muted */
+ left = 0;
+ else
+ left = ((a & 0x7f) * 100) / nsteps;
+ if (!corb_read
+ (mixer, volume_widget->cad, volume_widget->wid, 0, GET_GAIN (1, 0),
+ 0, &a, &b))
+ return OSS_EIO;
+ if (a & 0x80) /* Muted */
+ right = 0;
+ else
+ right = ((a & 0x7f) * 100) / nsteps;
+ v = left | (right << 8);
+ return *arg = v;
+ break;
+
+ case SNDCTL_DSP_SETPLAYVOL:
+ v = *arg;
+
+ left = v & 0xff;
+ right = (v >> 8) & 0xff;
+
+ if (left < 0)
+ left = 0;
+ if (left > 100)
+ left = 100;
+ if (right < 0)
+ right = 0;
+ if (right > 100)
+ right = 100;
+ v = left | (right << 8);
+
+ nsteps = (volume_widget->outamp_caps >> 8) & 0x3f;
+ if (nsteps < 1)
+ nsteps = 1;
+
+ a = (left * nsteps) / 100;
+ if (left == 0)
+ a |= 0x80; /* Mute */
+ corb_write (mixer, volume_widget->cad, volume_widget->wid, 0,
+ SET_GAIN (1, 0, 1, 0, 0), a);
+ if (linked_wid)
+ corb_write (mixer, volume_widget->cad, linked_wid, 0,
+ SET_GAIN (1, 0, 1, 0, 0), a);
+ a = (right * nsteps) / 100;
+ if (right == 0)
+ a |= 0x80; /* Mute */
+ corb_write (mixer, volume_widget->cad, volume_widget->wid, 0,
+ SET_GAIN (1, 0, 0, 1, 1), a);
+ if (linked_wid)
+ corb_write (mixer, volume_widget->cad, linked_wid, 0,
+ SET_GAIN (1, 0, 0, 1, 1), a);
+
+ mixer_devs[mixer->mixer_dev]->modify_counter++;
+ return *arg = v;
+ break;
+
+ case SNDCTL_DSP_MODEM_OFFHOOK:
+ if (!endpoint->is_modem)
+ {
+ return OSS_EINVAL;
+ }
+ v = *arg;
+ if (mixer->codecs[endpoint->cad]->vendor_flags & VF_SI3055_HACK)
+ {
+ v = hdaudio_si3055_set_offhook(mixer, endpoint->cad, v);
+ }
+ else
+ {
+ return OSS_ENOTSUP;
+ }
+ return *arg = v;
+ break;
+ }
+
+ return OSS_EINVAL;
+}
+
+/*
+ * Support routines for dedicated mixer drivers
+ */
+
+void
+hda_codec_add_group (int dev, hdaudio_mixer_t * mixer, int cad, int *group,
+ int parent_group, const char *name)
+{
+ int grp;
+
+ if ((grp = mixer_ext_create_group (dev, parent_group, name)) > 0)
+ *group = grp;
+}
+
+int
+hda_codec_add_pingroup (int dev, hdaudio_mixer_t * mixer, int cad, int wid,
+ int *group, int top_group, int *parent_group,
+ const char *name, int *n, const char *parent_name,
+ int group_size)
+{
+ int grp;
+ widget_t *widget;
+ codec_t *codec;
+
+ if (cad < 0 || cad >= mixer->ncodecs)
+ return OSS_ENXIO;
+ codec = mixer->codecs[cad];
+
+ if (wid < 0 || wid >= codec->nwidgets)
+ return OSS_ENXIO;
+
+ widget = &codec->widgets[wid];
+ if (widget->used || widget->skip)
+ return 0;
+
+ if (group_size <= 1 || ((*n % group_size) == 0 && *n > 0))
+ {
+ if ((grp = mixer_ext_create_group (dev, top_group, parent_name)) > 0)
+ *parent_group = grp;
+ *n = 0;
+ }
+
+ if ((grp = mixer_ext_create_group (dev, *parent_group, name)) > 0)
+ *group = grp;
+ (*n)++;
+
+ return 1;
+}
+
+int
+hda_codec_add_adcgroup (int dev, hdaudio_mixer_t * mixer, int cad, int wid,
+ int *group, int top_group, int *parent_group,
+ const char *name, int *n, const char *parent_name,
+ int group_size)
+{
+ int grp;
+ widget_t *widget;
+ codec_t *codec;
+
+ if (cad < 0 || cad >= mixer->ncodecs)
+ return OSS_ENXIO;
+ codec = mixer->codecs[cad];
+
+ if (wid < 0 || wid >= codec->nwidgets)
+ return OSS_ENXIO;
+
+ widget = &codec->widgets[wid];
+ if (widget->used || widget->skip)
+ return 0;
+
+ if (group_size <= 1 || ((*n % group_size) == 0 && *n > 0))
+ {
+ if ((grp = mixer_ext_create_group (dev, top_group, parent_name)) > 0)
+ *parent_group = grp;
+ *n = 0;
+ }
+
+ if ((grp = mixer_ext_create_group (dev, *parent_group, name)) > 0)
+ *group = grp;
+ (*n)++;
+
+ return 1;
+}
+
+int
+hda_codec_add_miscgroup (int dev, hdaudio_mixer_t * mixer, int cad, int wid,
+ int *group, int top_group, int *parent_group,
+ const char *name, int *n, const char *parent_name,
+ int group_size)
+{
+ int grp;
+ widget_t *widget;
+ codec_t *codec;
+
+ if (cad < 0 || cad >= mixer->ncodecs)
+ return OSS_ENXIO;
+ codec = mixer->codecs[cad];
+
+ if (wid < 0 || wid >= codec->nwidgets)
+ return OSS_ENXIO;
+
+ widget = &codec->widgets[wid];
+ if (widget->used || widget->skip)
+ return 0;
+
+ if (group_size <= 1 || ((*n % group_size) == 0 && *n > 0))
+ {
+ if ((grp = mixer_ext_create_group (dev, top_group, parent_name)) > 0)
+ *parent_group = grp;
+ *n = 0;
+ }
+
+ if ((grp = mixer_ext_create_group (dev, *parent_group, name)) > 0)
+ *group = grp;
+ (*n)++;
+
+ return 1;
+}
+
+int
+create_outgain_selector (hdaudio_mixer_t * mixer, widget_t * widget,
+ int group, const char *name)
+{
+ int num = MIXNUM (widget, CT_OUTGAINSEL, 0);
+ int ctl, i;
+ int maxval;
+ int offs, step, range;
+ oss_mixext *ent;
+
+ char tmp[128], *t;
+
+ range =
+ ((widget->outamp_caps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) +
+ 1;
+ step =
+ ((widget->outamp_caps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) +
+ 1;
+ offs = (widget->outamp_caps >> AMPCAP_OFFSET_SHIFT) & AMPCAP_OFFSET_MASK;
+
+ maxval = range;
+
+ if (widget->outamp_caps & AMPCAP_MUTE)
+ maxval += 1;
+
+ if ((ctl = mixer_ext_create_control (mixer->mixer_dev,
+ group,
+ num, hdaudio_set_control,
+ MIXT_ENUM,
+ name, maxval,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+
+ t = tmp;
+ *t = 0;
+
+ for (i = 0; i < range; i++)
+ {
+ int v;
+
+ v = (i - offs) * step * 4;
+
+ if (*tmp != 0)
+ *t++ = ' ';
+
+ sprintf (t, "%d.%ddB", v / 10, v % 10);
+ t += strlen (t);
+ }
+
+ if (widget->outamp_caps & AMPCAP_MUTE)
+ {
+ if (*tmp != 0)
+ *t++ = ' ';
+ strcpy (t, "mute");
+ t += strlen (t);
+ }
+
+ mixer_ext_set_strings (mixer->mixer_dev, ctl, tmp, 0);
+ /* Copy RGB color */
+ if (widget->rgbcolor != 0)
+ if ((ent = mixer_find_ext (mixer->mixer_dev, ctl)) != NULL)
+ ent->rgbcolor = widget->rgbcolor;
+
+ return 0;
+}
+
+int
+create_ingain_selector (hdaudio_mixer_t * mixer, codec_t * codec,
+ widget_t * widget, int group, int ix,
+ const char *name)
+{
+ int num = MIXNUM (widget, CT_INGAINSEL, ix);
+
+ int ctl, i;
+ int maxval;
+ int offs, step, range;
+ oss_mixext *ent;
+
+ char tmp[128], *t;
+
+ range =
+ ((widget->inamp_caps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) +
+ 1;
+ step =
+ ((widget->inamp_caps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) +
+ 1;
+ offs = (widget->inamp_caps >> AMPCAP_OFFSET_SHIFT) & AMPCAP_OFFSET_MASK;
+
+ maxval = range;
+
+ if (widget->inamp_caps & AMPCAP_MUTE)
+ maxval += 1;
+
+ if ((ctl = mixer_ext_create_control (mixer->mixer_dev,
+ group,
+ num, hdaudio_set_control,
+ MIXT_ENUM,
+ name, maxval,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+
+ t = tmp;
+ *t = 0;
+
+ for (i = 0; i < range; i++)
+ {
+ int v;
+
+ v = (i - offs) * step * 4;
+
+ if (*tmp != 0)
+ *t++ = ' ';
+
+ sprintf (t, "%d.%ddB", v / 10, v % 10);
+ t += strlen (t);
+ }
+
+ if (widget->inamp_caps & AMPCAP_MUTE)
+ {
+ if (*tmp != 0)
+ *t++ = ' ';
+ strcpy (t, "mute");
+ t += strlen (t);
+ }
+
+ mixer_ext_set_strings (mixer->mixer_dev, ctl, tmp, 0);
+ /* Copy RGB color */
+ if (widget->color != 0)
+ if ((ent = mixer_find_ext (mixer->mixer_dev, ctl)) != NULL)
+ ent->rgbcolor = widget->rgbcolor;
+ return 0;
+}
+
+int
+hda_codec_add_outamp (int dev, hdaudio_mixer_t * mixer, int cad, int wid,
+ int group, const char *name, int percent,
+ unsigned int flags)
+{
+ widget_t *widget;
+ codec_t *codec;
+ int typ, num, maxval, val, ctl = 0;
+ int range, step;
+ oss_mixext *ent;
+ extern int mixer_muted;
+
+ if (cad < 0 || cad >= mixer->ncodecs)
+ return OSS_ENXIO;
+ codec = mixer->codecs[cad];
+
+ if (wid < 0 || wid >= codec->nwidgets)
+ return OSS_ENXIO;
+
+ widget = &codec->widgets[wid];
+
+ 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 ((ctl = mixer_ext_create_control (mixer->mixer_dev,
+ group,
+ num, hdaudio_set_control,
+ typ,
+ name, maxval,
+ flags | MIXF_READABLE |
+ MIXF_WRITEABLE |
+ MIXF_CENTIBEL)) < 0)
+ return ctl;
+
+ /* setup volume */
+ val = (maxval * percent) / 100;
+ val = val | (val << 16);
+ if (mixer_muted)
+ val = 0;
+ /* Copy RGB color */
+ if (widget->rgbcolor != 0)
+ if ((ent = mixer_find_ext (dev, ctl)) != NULL)
+ ent->rgbcolor = widget->rgbcolor;
+ hdaudio_set_control (mixer->mixer_dev, num, SNDCTL_MIX_WRITE, val);
+ }
+
+ return ctl;
+}
+
+int
+hda_codec_add_outmute (int dev, hdaudio_mixer_t * mixer, int cad, int wid,
+ int group, const char *name, int muted)
+{
+ widget_t *widget;
+ codec_t *codec;
+ int ctl = 0;
+ oss_mixext *ent;
+
+ if (cad < 0 || cad >= mixer->ncodecs)
+ return OSS_ENXIO;
+ codec = mixer->codecs[cad];
+
+ if (wid < 0 || wid >= codec->nwidgets)
+ return OSS_ENXIO;
+
+ widget = &codec->widgets[wid];
+
+ if (widget->outamp_caps & AMPCAP_MUTE) /* Only mute control */
+ {
+ // name = "mute";
+
+ if ((ctl = 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 ctl;
+ /* Copy RGB color */
+ if (widget->rgbcolor != 0)
+ if ((ent = mixer_find_ext (dev, ctl)) != NULL)
+ ent->rgbcolor = widget->rgbcolor;
+
+ hdaudio_set_control (mixer->mixer_dev,
+ MIXNUM (widget, CT_OUTMUTE, 0),
+ SNDCTL_MIX_WRITE, muted);
+ return ctl;
+ }
+ return 0;
+}
+
+int
+hda_codec_add_inamp (int dev, hdaudio_mixer_t * mixer, int cad, int wid,
+ int ix, int group, const char *name, int percent, int flags)
+{
+ widget_t *widget;
+ widget_t *src_widget;
+ codec_t *codec;
+ int typ, num, maxval, val, ctl = 0, range, step;
+ oss_mixext *ent;
+ extern int mixer_muted;
+
+ if (cad < 0 || cad >= mixer->ncodecs)
+ return OSS_ENXIO;
+ codec = mixer->codecs[cad];
+
+ if (wid < 0 || wid >= codec->nwidgets)
+ return OSS_ENXIO;
+
+ widget = &codec->widgets[wid];
+
+ 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)
+ {
+ return create_ingain_selector (mixer, codec, widget, group, ix, name);
+ }
+ maxval = hdaudio_amp_maxval (widget->inamp_caps);
+
+ if (widget->widget_caps & WCAP_STEREO)
+ {
+ typ = MIXT_STEREOSLIDER16;
+ num = MIXNUM (widget, CT_INSTEREO, ix);
+ }
+ else
+ {
+ typ = MIXT_MONOSLIDER16;
+ num = MIXNUM (widget, CT_INMONO, ix);
+ }
+
+ if (codec->widgets[widget->connections[ix]].skip)
+ {
+ hdaudio_set_control (mixer->mixer_dev, num, SNDCTL_MIX_WRITE, 0);
+ return 0;
+ }
+
+ if ((ctl = mixer_ext_create_control (mixer->mixer_dev,
+ group,
+ num,
+ hdaudio_set_control,
+ typ,
+ name, maxval,
+ MIXF_READABLE |
+ MIXF_WRITEABLE | MIXF_CENTIBEL | flags)) < 0)
+ return ctl;
+
+ /* Setup initial volume */
+ val = (maxval * percent) / 100;
+ val = val | (val << 16);
+ if (mixer_muted)
+ val = 0;
+
+ hdaudio_set_control (mixer->mixer_dev, num, SNDCTL_MIX_WRITE, val);
+
+ /* Copy RGB color */
+ src_widget = &codec->widgets[widget->connections[ix]];
+ if (src_widget->rgbcolor != 0)
+ if ((ent = mixer_find_ext (dev, ctl)) != NULL)
+ ent->rgbcolor = src_widget->rgbcolor;
+
+ return ctl;
+}
+
+int
+hda_codec_add_inmute (int dev, hdaudio_mixer_t * mixer, int cad, int wid,
+ int ix, int group, const char *name, int muted, unsigned int flags)
+{
+ widget_t *widget;
+ codec_t *codec;
+ oss_mixext *ent;
+ int ctl = 0;
+
+ if (cad < 0 || cad >= mixer->ncodecs)
+ return OSS_ENXIO;
+ codec = mixer->codecs[cad];
+
+ if (wid < 0 || wid >= codec->nwidgets)
+ return OSS_ENXIO;
+
+ widget = &codec->widgets[wid];
+
+ if (codec->widgets[widget->connections[ix]].skip)
+ {
+ int num = MIXNUM (widget, CT_INMUTE, ix);
+ hdaudio_set_control (mixer->mixer_dev, num, SNDCTL_MIX_WRITE, 1);
+ return 0;
+ }
+
+ if ((ctl = mixer_ext_create_control (mixer->mixer_dev,
+ group,
+ MIXNUM (widget,
+ CT_INMUTE, ix),
+ hdaudio_set_control,
+ MIXT_MUTE, name, 2,
+ flags | MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+ /* Copy RGB color */
+ if (widget->rgbcolor != 0)
+ if ((ent = mixer_find_ext (dev, ctl)) != NULL)
+ ent->rgbcolor = widget->rgbcolor;
+
+ hdaudio_set_control (mixer->mixer_dev,
+ MIXNUM (widget, CT_INMUTE, ix),
+ SNDCTL_MIX_WRITE, muted);
+ return ctl;
+}
+
+int
+hda_codec_set_inmute (int dev, hdaudio_mixer_t * mixer, int cad, int wid,
+ int ix, int group, const char *name, int muted)
+{
+ widget_t *widget;
+ codec_t *codec;
+
+ if (cad < 0 || cad >= mixer->ncodecs)
+ return OSS_ENXIO;
+ codec = mixer->codecs[cad];
+
+ if (wid < 0 || wid >= codec->nwidgets)
+ return OSS_ENXIO;
+
+ widget = &codec->widgets[wid];
+
+// cmn_err(CE_CONT, "Set inmute 0x%02x:%d=%d\n", wid, ix, muted);
+ return hdaudio_set_control (mixer->mixer_dev,
+ MIXNUM (widget, CT_INMUTE, ix),
+ SNDCTL_MIX_WRITE, muted);
+}
+
+int
+hda_codec_add_insrc (int dev, hdaudio_mixer_t * mixer, int cad, int wid,
+ int ix, int group, const char *name, int unselected)
+{
+ widget_t *widget;
+ codec_t *codec;
+ oss_mixext *ent;
+ int ctl = 0;
+
+ if (cad < 0 || cad >= mixer->ncodecs)
+ return OSS_ENXIO;
+ codec = mixer->codecs[cad];
+
+ if (wid < 0 || wid >= codec->nwidgets)
+ return OSS_ENXIO;
+
+ widget = &codec->widgets[wid];
+
+ if (codec->widgets[widget->connections[ix]].skip)
+ {
+ int num = MIXNUM (widget, CT_INMUTE, ix);
+
+ hdaudio_set_control (mixer->mixer_dev, num, SNDCTL_MIX_WRITE, 1);
+ return 0;
+ }
+
+ if ((ctl = mixer_ext_create_control (mixer->mixer_dev,
+ group,
+ MIXNUM (widget,
+ CT_INSRC, ix),
+ hdaudio_set_control,
+ MIXT_ONOFF, name, 2,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return ctl;
+ /* Copy RGB color */
+ if (widget->rgbcolor != 0)
+ if ((ent = mixer_find_ext (dev, ctl)) != NULL)
+ ent->rgbcolor = widget->rgbcolor;
+
+ hdaudio_set_control (mixer->mixer_dev,
+ MIXNUM (widget, CT_INSRC, ix),
+ SNDCTL_MIX_WRITE, unselected);
+ return ctl;
+}
+
+int
+hda_codec_add_insrcselect (int dev, hdaudio_mixer_t * mixer, int cad, int wid,
+ int group, int *ctl, const char *name,
+ int initial_selection)
+{
+ widget_t *widget;
+ codec_t *codec;
+ int i;
+ oss_mixext *ext;
+
+ *ctl = 0;
+
+ if (cad < 0 || cad >= mixer->ncodecs)
+ return OSS_ENXIO;
+ codec = mixer->codecs[cad];
+
+ if (wid < 0 || wid >= codec->nwidgets)
+ return OSS_ENXIO;
+
+ widget = &codec->widgets[wid];
+
+ if ((*ctl = mixer_ext_create_control (mixer->mixer_dev,
+ group,
+ MIXNUM (widget,
+ CT_INSRCSELECT, 0),
+ hdaudio_set_control,
+ MIXT_ENUM, name, widget->nconn,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return 0;
+
+ ext = mixer_find_ext (mixer->mixer_dev, *ctl);
+ if (ext == NULL)
+ {
+ cmn_err (CE_WARN, "Cannot locate the mixer extension (x)\n");
+ return OSS_EIO;
+ }
+
+ /* Copy RGB color */
+ if (widget->color != 0)
+ ext->rgbcolor = widget->rgbcolor;
+
+ memset (ext->enum_present, 0, sizeof (ext->enum_present));
+
+ for (i = 0; i < widget->nconn; i++)
+ {
+
+ /*
+ * 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;
+
+ /*
+ * 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));
+ }
+
+ hdaudio_set_control (mixer->mixer_dev,
+ MIXNUM (widget, CT_INSRCSELECT, 0),
+ SNDCTL_MIX_WRITE, initial_selection);
+ return 1;
+}
+
+int
+hda_codec_add_select (int dev, hdaudio_mixer_t * mixer, int cad, int wid,
+ int group, const char *name, int *ctl,
+ int initial_select)
+{
+ widget_t *widget;
+ codec_t *codec;
+ oss_mixext *ext;
+ int i;
+
+ *ctl = 0;
+
+ if (cad < 0 || cad >= mixer->ncodecs)
+ return OSS_ENXIO;
+ codec = mixer->codecs[cad];
+
+ if (wid < 0 || wid >= codec->nwidgets)
+ return OSS_ENXIO;
+
+ widget = &codec->widgets[wid];
+
+ 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;
+
+ ext = mixer_find_ext (mixer->mixer_dev, *ctl);
+
+ if (ext == NULL)
+ {
+ cmn_err (CE_WARN, "Cannot locate the mixer extension (x)\n");
+ return OSS_EIO;
+ }
+ /* Copy RGB color */
+ if (widget->color != 0)
+ ext->rgbcolor = widget->rgbcolor;
+
+ memset (ext->enum_present, 0, sizeof (ext->enum_present));
+
+ for (i = 0; i < widget->nconn; i++)
+ {
+
+ /*
+ * 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;
+
+ /*
+ * 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));
+ }
+
+ if (initial_select > -1)
+ widget->current_selector = initial_select;
+
+ if (widget->current_selector >= widget->nconn)
+ widget->current_selector = 0;
+ corb_write (mixer, widget->cad, widget->wid, 0, SET_SELECTOR,
+ widget->current_selector);
+
+ return *ctl;
+}
+
+int
+hda_codec_add_pinselect (int dev, hdaudio_mixer_t * mixer, int cad, int wid,
+ int group, const char *name, int *ctl,
+ int initial_select)
+{
+ widget_t *widget;
+ codec_t *codec;
+ unsigned int conf, b;
+ oss_mixext *ext;
+ int i;
+
+ *ctl = 0;
+
+ if (cad < 0 || cad >= mixer->ncodecs)
+ return OSS_ENXIO;
+ codec = mixer->codecs[cad];
+
+ if (wid < 0 || wid >= codec->nwidgets)
+ return OSS_ENXIO;
+
+ widget = &codec->widgets[wid];
+
+ if ((*ctl = mixer_ext_create_control (mixer->mixer_dev,
+ group,
+ MIXNUM (widget, CT_SELECT, 0),
+ hdaudio_set_control,
+ MIXT_ENUM,
+ name,
+ widget->nconn + 1,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return *ctl;
+
+ ext = mixer_find_ext (mixer->mixer_dev, *ctl);
+
+ if (ext == NULL)
+ {
+ cmn_err (CE_WARN, "Cannot locate the mixer extension (x)\n");
+ return OSS_EIO;
+ }
+
+ /* Copy RGB color */
+ if (widget->color != 0)
+ ext->rgbcolor = widget->rgbcolor;
+ memset (ext->enum_present, 0, sizeof (ext->enum_present));
+
+ for (i = 0; i < widget->nconn; i++)
+ {
+
+ /*
+ * 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;
+
+ /*
+ * 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));
+ }
+
+ /*
+ * Enable the input selection (if available)
+ */
+ i = widget->nconn;
+ if (widget->pincaps & PINCAP_INPUT_CAPABLE)
+ ext->enum_present[i / 8] |= (1 << (i % 8));
+
+/*
+ * Set the initial value.
+ *
+ * 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;
+ }
+
+ }
+ }
+ else if (widget->sensed_pin == PIN_IN || widget->sensed_pin == PIN_MIC)
+ widget->current_selector = widget->nconn; /* Turn on input mode */
+
+ if (initial_select > -1)
+ widget->current_selector = initial_select;
+
+ if (widget->current_selector < 0
+ || widget->current_selector >= widget->nconn + 1)
+ widget->current_selector = 0;
+
+ if (widget->current_selector < widget->nconn)
+ {
+ /* Output source select */
+ corb_write (mixer, cad, wid, 0, SET_SELECTOR, widget->current_selector);
+ /* Enable output and HP amp. Set vref=Ground */
+ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0xc0);
+ }
+ else
+ {
+ /* Input select
+ * Program the correct VRef Values
+ */
+
+ if (widget->pin_type == PIN_IN) /* Line-in */
+ {
+ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x20); /*Ground*/
+ }
+ else /* Mic-in */
+ {
+ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x24); /*Vref=8
+ 0% */
+ }
+ }
+
+ return *ctl;
+}
+
+void
+hda_codec_set_select (int dev, hdaudio_mixer_t * mixer, int cad, int wid,
+ int value)
+{
+ codec_t *codec;
+ widget_t *widget;
+
+ if (cad < 0 || cad >= mixer->ncodecs)
+ return;
+ codec = mixer->codecs[cad];
+
+ if (wid < 0 || wid >= codec->nwidgets)
+ return;
+
+ widget = &codec->widgets[wid];
+
+ widget->current_selector = value;
+
+ corb_write (mixer, cad, wid, 0, SET_SELECTOR, value);
+}
+
+void
+hda_codec_set_pinselect (int dev, hdaudio_mixer_t * mixer, int cad, int wid,
+ int value)
+{
+ codec_t *codec;
+ widget_t *widget;
+
+ if (cad < 0 || cad >= mixer->ncodecs)
+ return;
+ codec = mixer->codecs[cad];
+
+ if (wid < 0 || wid >= codec->nwidgets)
+ return;
+
+ widget = &codec->widgets[wid];
+
+ widget->current_selector = value;
+
+ if (widget->current_selector < widget->nconn)
+ {
+ /* Output source select */
+ corb_write (mixer, cad, wid, 0, SET_SELECTOR, widget->current_selector);
+ /* Enable output and HP amp. Set vref=Ground */
+ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0xc0);
+ }
+ else
+ {
+ /* Input select
+ * Program the correct VRef Values
+ */
+
+ if (widget->pin_type == PIN_IN) /* Line-in */
+ {
+ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x20); /*Ground*/
+ }
+ else /* Mic-in */
+ {
+ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x24); /*Vref=8
+ 0% */
+ }
+ }
+}
+
+int
+hda_codec_add_choices (int dev, hdaudio_mixer_t * mixer, int ctl,
+ const char *choiselist)
+{
+ mixer_ext_set_strings (dev, ctl, choiselist, 0);
+
+ return 0;
+}
+
+void
+hda_codec_set_color(int dev, hdaudio_mixer_t *mixer, int ctl, int color)
+{
+ oss_mixext *ext;
+
+ if ((ext = mixer_find_ext (mixer->mixer_dev, ctl)) != NULL)
+ {
+ ext->rgbcolor = color;
+//cmn_err(CE_CONT, "Mixer %s rgb->%06x\n", ext->extname, ext->rgbcolor);
+ }
+}