/*
 * 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 software is released under the BSD license.
 * 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;
	  }

	  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;
	  }

	  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);
  }
}
