/*
 * Purpose: Codec handling for Intel High Definition Audio (HDA/Azalia).
 */
/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2007.
 *
 * This software is released under CDDL 1.0 source license for operating systems
 * that have their full source code available under the BSD or CDDL licenses.
 *
 * See the COPYING file included in the main directory of this source
 * distribution. Please contact sales@opensound.com for further info.
 */

/*
 * NOTE! This is not the development version of this file. Please download the
 * sources of the latest Open Sound System version from 
 * http://developer.opensound.com/sources if you plan to make any changes
 * to the software.
 */

#include "hdaudio_cfg.h"
#include "hdaudio.h"
#include "hdaudio_codec.h"

extern int hdaudio_snoopy;
extern int hdaudio_jacksense;

struct codec_desc
{
  unsigned int id;
  char *name;
  unsigned long flags;
#define VF_NONE		0x00000000
#define VF_ALC88X_HACK	0x00000001	/* ALC88x requires special handling for S/PDIF */
  char **remap;

  /*
   * Order of the output converter (DAC) widgets in multi channel mode.
   * If left to 0 then 0x76543210 (sequential order) is assumed. Some
   * motherboards are known to use their own channel ordering which can be
   * fixed using this approach.
   */
  unsigned int multich_map;
  hda_mixer_init_func mixer_init;
};

/*
 * Meaningfull widget naming schemes for some known codecs.
 * The list is terminated by a NULL string. An empty string
 * ("") means that the automatically selected name will be used.
 */

static const char *alc880remap[] = {
  "",				/* 0 */
  "",				/* 1 */
  "play-front",			/* 2 */
  "play-rear",			/* 3 */
  "play-center/LFE",		/* 4 */
  "play-side",			/* 5 */
  "digital-out",		/* 6 */
  "rec1",			/* 7 */
  "rec2",			/* 8 */
  "rec3",			/* 9 */
  "digital-in",			/* 10 */
  "inputmix",			/* 11 */
  "front",			/* 12 */
  "rear",			/* 13 */
  "center/LFE",			/* 14 */
  "side",			/* 15 */

  "source-a",			/* 16 */
  "source-b",			/* 17 */
  "source-c",			/* 18 */
  "source-d",			/* 19 */

  "green",			/* 20 */
  "black",			/* 21 */
  "orange",			/* 22 */
  "purple",			/* 23 */
  "pink",			/* 24 */
  "fp-pink",			/* 25 */
  "blue",			/* 26 */
  "fp-green",			/* 27 */
  "cd",				/* 28 */
  "beep",			/* 29 */
  "spdifout",			/* 30 */
  "spdifin",			/* 31 */
  NULL
};

static const char *alc260remap[] = {
  "",				/* 0x00 */
  "",				/* 0x01 */
  "play",			/* 0x02 */
  "digital-out",		/* 0x03 */
  "rec1",			/* 0x04 */
  "rec2",			/* 0x05 */
  "digital-in",			/* 0x06 */
  "inputmix",			/* 0x07 (Input mixer) */
  "headphone-mix",		/* 0x08 */
  "speaker-mix",		/* 0x09 */
  "mono-mix",			/* 0x0a */
  "",				/* 0x0b */
  "",				/* 0x0c */
  "",				/* 0x0d */
  "",				/* 0x0e */
  "",				/* 0x0f (Speakers?) */
  "",				/* 0x10 (Headphone?) */
  "mono",			/* 0x11 */
  "",				/* 0x12 (Mic1?) */
  "",				/* 0x13 (Mic2?) */
  "",				/* 0x14 (Line1?) */
  "",				/* 0x15 (Line2?) */
  "cd",				/* 0x16 */
  "beep",			/* 0x17 */
  "",				/* 0x18 (S/PDIF out) */
  "",				/* 0x19 (S/PDIF in) */
  NULL
};

static const char *alc861remap[] = {
  "",				/* 0x00 */
  "",				/* 0x01 */
  "wid02",			/* 0x02 */
  "play-front",			/* 0x03 */
  "play-side",			/* 0x04 */
  "play-center/LFE",		/* 0x05 */
  "play-rear",			/* 0x06 */
  "spdout",			/* 0x07 */
  "rec",			/* 0x08 */
  "wid09",			/* 0x09 */
  "wid0a",			/* 0x0a */
  "",				/* 0x0b */
  "",				/* 0x0c */
  "",				/* 0x0d */
  "",				/* 0x0e */
  "",				/* 0x0f */
  "",				/* 0x10 */
  "",				/* 0x11 */
  "",				/* 0x12 */
  "wid13",			/* 0x13 */
  "recmix1",			/* 0x14 */
  "outmix",			/* 0x15 */
  "frontmix",			/* 0x16 */
  "sidemix",			/* 0x17 */
  "c/l-mix",			/* 0x18 */
  "rearmix",			/* 0x19 */
  "line2-mix",			/* 0x1a */
  "mic2mix",			/* 0x1b */
  "line1mix",			/* 0x1c */
  "wid1d",			/* 0x1d */
  "vendor",			/* 0x1e */
  "",				/* 0x1f */
  "",				/* 0x20 */
  "",				/* 0x21 */
  "",				/* 0x22 */
  "beep",			/* 0x23 */
  NULL
};

static const char *cmi9880remap[] = {
  "",				/* 0 */
  "",				/* 1 */
  "",				/* 2 */
  "play-front",			/* 3 */
  "play-rear",			/* 4 */
  "play-side",			/* 5 */
  "play-center/lfe",		/* 6 */
  "spdif-out",			/* 7 */
  "",				/* 8 */
  "",				/* 9 */
  "",				/* 10 */
  "",				/* 11 */
  "",				/* 12 */
  "",				/* 13 */
  "",				/* 14 */
  "",				/* 15 */
  "",				/* 16 */
  "",				/* 17 */
  "",				/* 18 */
  "",				/* 19 */
  "",				/* 20 */
  "",				/* 21 */
  "",				/* 22 */
  "pcbeep",			/* 23 */
  "",				/* 24 */
  "",				/* 25 */
  "",				/* 26 */
  "",				/* 27 */
  "",				/* 28 */
  "",				/* 29 */
  "",				/* 30 */
  "",				/* 31 */
  NULL
};

static const char *ad1981remap[] = {
  "",				/* 0 */
  "",				/* 1 */
  "spdif",			/* 2 */
  "play-front",			/* 3 */
  "record",			/* 4 */
  "line-out",			/* 5 */
  "headphone-out",		/* 6 */
  "mono-out",			/* 7 */
  "mic-front",			/* 8 */
  "line-in",			/* 9 */
  "spdif-out",			/* a */
  "monoout-sel",		/* b */
  "mic-mix",			/* c */
  "pcbeep-sel",			/* d */
  "rec-mix",			/* e */
  "mono-mix",			/* f */
  "digital-beep",		/* 10 */
  "frontmix-amp",		/* 11 */
  "mic-mixamp",			/* 12 */
  "linein-mixamp",		/* 13 */
  "powerdown",			/* 14 */
  "rec-sel",			/* 15 */
  "",				/* 16 */
  "",				/* 17 */
  "",				/* 18 */
  "",				/* 19 */
  "lineout-mixamp",		/* 1a */
  "aux-mixamp",			/* 1b */
  "micrear-mixamp",		/* 1c */
  "CD-mixamp",			/* 1d */
  "mic-front-mute",		/* 1e */
  "mic-rear-mute",		/* 1f */
  NULL
};

static const char *ad1986remap[] = {
  "",				/* 0 */
  "",				/* 1 */
  "spdif",			/* 2 */
  "play-front",			/* 3 */
  "play-side",			/* 4 */
  "play-center/LFE",		/* 5 */
  "record",			/* 6 */
  "recmon",			/* 7 */
  "mono",			/* 8 */
  "downmix",			/* 9 */
  "headphone",			/* a */
  "lineout",			/* b */
  "side",			/* c */
  "center-lfe",			/* d */
  "mono-sel",			/* e */
  "mic",			/* f */
  "linein",			/* 10 */
  "mic-swap",			/* 11 */
  "rec-src"			/* 12 */
    "mic-mix",			/* 13 */
  "phone-mix",			/* 14 */
  "cd-mix",			/* 15 */
  "aux-mix",			/* 16 */
  "linein-mix",			/* 17 */
  "pcbeep",			/* 18 */
  "digital-beep"		/* 19 */
    "",				/* 1a */
  "",				/* 1b */
  "",				/* 1c */
  "",				/* 1d */
  "",				/* 1e */
  "",				/* 1f */
  "",				/* 20 */
  "",				/* 21 */
  "",				/* 22 */
  "",				/* 23 */
  "",				/* 24 */
  "spdifout",			/* 25 */
  "analog-powerdown",		/* 26 */
  "mic-centerlfe-mix",		/* 27 */
  "mic-linein-mix",		/* 28 */
  "center-linein-mix",		/* 29 */
  "mic-linein-center-mix",	/* 2a */
  "mic1/2mix",			/* 2b */
  NULL
};

static const char *ad1988remap[] = {
  "",				/* 0 */
  "",				/* 1 */
  "play-spdif",			/* 2 */
  "play-headph",		/* 3 */
  "play-front",			/* 4 */
  "play-center/LFE",		/* 5 */
  "play-rear",			/* 6 */
  "rec-spdif",			/* 7 */
  "rec1",			/* 8 */
  "rec2",			/* 9 */
  "play-side",			/* a */
  "spdin-src",			/* b */
  "rec1-src",			/* c */
  "rec2-src",			/* d */
  "rec3-src",			/* e */
  "rec3",			/* f */
  "pcbeep",			/* 10 */
  "",				/* 11 */
  ""				/* 12 */
    "",				/* 13 */
  "",				/* 14 */
  "",				/* 15 */
  "",				/* 16 */
  "",				/* 17 */
  "cd-in",			/* 18 */
  "wid19",			/* 19 */
  "pcbeep-in",			/* 1a */
  "spdin",			/* 1b */
  "spdout",			/* 1c */
  "spdout-mix",			/* 1d */
  "mono-mix",			/* 1e */
  "vol-knob",			/* 1f */
  "analog-mix",			/* 20 */
  "out-attn",			/* 21 */
  "headph-mix",			/* 22 */
  "inmix",			/* 23 */
  "",				/* 24 */
  "",				/* 25 */
  "porte-mix",			/* 26 */
  "portg-mix",			/* 27 */
  "porth-mix",			/* 28 */
  "portd-mix",			/* 29 */
  "portf-mix",			/* 2a */
  "portb-mix",			/* 2b */
  "portc-mix",			/* 2c */
  "stereo-mono",		/* 2d */
  "",				/* 2e (unused) */
  "bias-pdown",			/* 2f */
  "portb-outsel",		/* 30 */
  "portc-outsel",		/* 31 */
  "porte-outsel",		/* 32 */
  "portc-insel",		/* 33 */
  "porte-insel",		/* 34 */
  "",				/* 35 (unused) */
  "mono-out-sel",		/* 36 */
  "porta-outsel",		/* 37 */
  "porta-boost",		/* 38 */
  "portb-boost",		/* 39 */
  "portc-boost",		/* 3a */
  "portf-boost",		/* 3b */
  "porte-boost",		/* 3c */
  "portd-boost",		/* 3d */
  NULL
};

static const char *stac9200remap[] = {
  "",				/* 0 */
  "",				/* 0x01 */
  "play",			/* 0x02 */
  "rec",			/* 0x03 */
  "digital-in",			/* 0x04 */
  "digital-out",		/* 0x05 */
  "reserved",			/* 0x06 */
  "playmux",			/* 0x07 */
  "spdifout",			/* 0x08 */
  "spdifin",			/* 0x09 */
  "recmux",			/* 0x0a */
  "mainvol",			/* 0x0b */
  "inputmux",			/* 0x0c */
  "headphone",			/* 0x0d */
  "line-out",			/* 0x0e */
  "line-in",			/* 0x0f */

  /* Pin widgets */
  "mic",			/* 0x10 */
  "mono-out",			/* 0x11 */
  "cd",				/* 0x12 */
  "mono-mix",			/* 0x13 */
  "pcbeep",			/* 0x14 */

  /* Reserved widgets */
  "",				/* 0x15 */
  "",				/* 0x16 */
  "",				/* 0x17 */
  "",				/* 0x18 */
  "",				/* 0x19 */
  NULL
};

static const char *stac922xremap[] = {
  "",				/* 0 */
  "",				/* 0x01 */
  "play-front",			/* 0x02 */
  "play-center/LFE",		/* 0x03 */
  "play-side",			/* 0x04 */
  "play-rear",			/* 0x05 */
  "rec1",			/* 0x06 */
  "rec2",			/* 0x07 */
  "digital-out",		/* 0x08 */
  "digital-in",			/* 0x09 */

  /* 0x0a-0x0f are pin widgets which are supposed to be self-identifying */
  "",				/* 0x0a */
  "",				/* 0x0b */
  "",				/* 0x0c */
  "",				/* 0x0d */
  "",				/* 0x0e */
  "",				/* 0x0f */

  "spdoutpin",			/* 0x10 */
  "spdinpin",			/* 0x11 */
  "rec1mux",			/* 0x12 */
  "rec2mux",			/* 0x13 */
  "pcbeep",			/* 0x14 */
  "cd",				/* 0x15 */
  "mainvol",			/* 0x16 */
  "rec1vol",			/* 0x17 */
  "rec2vol",			/* 0x18 */
  "adatout",			/* 0x19 */
  "i2sout",			/* 0x1a */
  "i2sin",			/* 0x1b */
  NULL
};

static const codec_desc_t codecs[] = {
  /* Realtek HDA codecs */
  {0x10ec0260, "ALC260", VF_NONE, (char **) &alc260remap},
  {0x10ec0262, "ALC262", VF_NONE},
  {0x10ec0880, "ALC880", VF_ALC88X_HACK,
   (char **) &alc880remap},
  {0x10ec0882, "ALC882", VF_ALC88X_HACK,
   (char **) &alc880remap},
  {0x10ec0883, "ALC883", VF_ALC88X_HACK,
   (char **) &alc880remap},
  {0x10ec0885, "ALC885", VF_ALC88X_HACK,
   (char **) &alc880remap},
  {0x10ec0888, "ALC888", VF_ALC88X_HACK,
   (char **) &alc880remap},
  {0x10ec0861, "ALC861", VF_NONE, (char **) &alc861remap},

  /* CMedia HDA codecs */
  {0x13f69880, "CMI9880", VF_NONE, (char **) &cmi9880remap},
  {0x434d4980, "CMI9880", VF_NONE, (char **) &cmi9880remap},

  /* Analog Devices HDA codecs */
  {0x11d41981, "AD1981", VF_NONE, (char **) &ad1981remap, 0x76543201},
  {0x11d41983, "AD1983", VF_NONE},
  {0x11d41986, "AD1986A", VF_NONE, (char **) &ad1986remap, 0x76540321},
  {0x11d41988, "AD1988", VF_NONE, (char **) &ad1988remap, 0x76015432},

  /* Sigmatel HDA codecs (some of them) */
  {0x83847690, "STAC9200", VF_NONE, (char **) &stac9200remap},
  {0x83847882, "STAC9220 A1", VF_NONE, (char **) &stac922xremap},
  {0x83847680, "STAC9221 A1", VF_NONE, (char **) &stac922xremap},
  {0x83847880, "STAC9220 A2", VF_NONE, (char **) &stac922xremap},
  {0x83847681, "STAC9220D A2", VF_NONE, (char **) &stac922xremap},
  {0x83847682, "STAC9221 A2", VF_NONE, (char **) &stac922xremap},
  {0x83847683, "STAC9221D A2", VF_NONE, (char **) &stac922xremap},
  {0x83847620, "STAC9274", VF_NONE},
  {0x83847621, "STAC9274D", VF_NONE},
  {0x83847622, "STAC9273X", VF_NONE},
  {0x83847623, "STAC9273D", VF_NONE},
  {0x83847624, "STAC9272X", VF_NONE},
  {0x83847625, "STAC9272D", VF_NONE},
  {0x83847626, "STAC9271X", VF_NONE},
  {0x83847627, "STAC9271D", VF_NONE},
  {0x83847628, "STAC9274X5NH", VF_NONE},
  {0x83847629, "STAC9274D5NH", VF_NONE},

  {0x838476a0, "STAC92xx", VF_NONE},

  /* Conexant */
  {0x14f15045, "SmartAMC HD2", VF_NONE, NULL, 0x76543201},

  {0x14f12bfa, "SmartCP", VF_NONE, NULL},
  /* Unknown */
  {0, "Unknown"}
};

/*
 * Table for subsystem ID's that require special handling
 */
static const codec_desc_t subdevices[] = {
  {0x98801019, "ECS 915P-A", VF_NONE, NULL, 0x76541320},
  {0, "Unknown"}
};

int
hdaudio_mixer_get_outendpoints (hdaudio_mixer_t * mixer,
				hdaudio_endpointinfo_t ** endpoints, int size)
{
  if (size != sizeof (hdaudio_endpointinfo_t))
    {
      cmn_err (CE_WARN, "hdaudio: Bad endpoint size\n");
      return -EIO;
    }

  *endpoints = (hdaudio_endpointinfo_t *) & mixer->outendpoints;
  return mixer->num_outendpoints;
}

int
hdaudio_mixer_get_inendpoints (hdaudio_mixer_t * mixer,
			       hdaudio_endpointinfo_t ** endpoints, int size)
{
  if (size != sizeof (hdaudio_endpointinfo_t))
    {
      cmn_err (CE_WARN, "hdaudio: Bad endpoint size\n");
      return -EIO;
    }

  *endpoints = (hdaudio_endpointinfo_t *) & mixer->inendpoints;
  return mixer->num_inendpoints;
}

static int
hda_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg)
{
  return *arg = 0;
}

static mixer_driver_t hda_mixer_driver = {
  hda_mixer_ioctl
};

static void
check_names (hdaudio_mixer_t * mixer)
{
  int c, c2, w, w2;
  int n, start;

  for (c = 0; c < mixer->ncodecs; c++)
    if (mixer->codecs[c] != NULL)
      {
	for (w = 1; w < mixer->codecs[c]->nwidgets; w++)
	  {
	    char tmp[16];
	    n = 0;

	    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 (strcmp (tmp, mixer->codecs[c2]->widgets[w2].name) == 0)
		    n++;

		start = 1;
	      }

	    if (n > 0)
	      {
		n = 0;
		start = w;
		for (c2 = c; c2 < mixer->ncodecs; c2++)
		  {
		    for (w2 = start; w2 < mixer->codecs[c2]->nwidgets; w2++)
		      if (strcmp (tmp, mixer->codecs[c2]->widgets[w2].name) ==
			  0)
			{
			  n++;
			  sprintf (mixer->codecs[c2]->widgets[w2].name,
				   "%s%d", tmp, n);
			}

		    start = 1;
		  }
	      }
	  }
      }

/*
 * Check mixer element names
 */

  for (c = 0; c < mixer->ncodecs; c++)
    if (mixer->codecs[c] != NULL)
      {
	for (w = 1; w < mixer->codecs[c]->nwidgets; w++)
	  {
	    widget_t *widget = &mixer->codecs[c]->widgets[w];

	    if (widget->wid_type != NT_MIXER)	/* Mixer */
	      continue;

	    if (widget->nconn != 1)
	      continue;

	    strcpy (widget->name,
		    mixer->codecs[c]->widgets[widget->connections[0]].name);
	  }
      }
}

int
hdaudio_amp_maxval (unsigned int ampcaps)
{
  int offs, step, range, maxval;

  range = ((ampcaps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) + 1;
  step = ((ampcaps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) + 1;
  offs = (ampcaps >> AMPCAP_OFFSET_SHIFT) & AMPCAP_OFFSET_MASK;

  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 offs, step, range, maxval, origv = v;

  range = ((ampcaps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) + 1;
  step = ((ampcaps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) + 1;
  offs = (ampcaps >> AMPCAP_OFFSET_SHIFT) & AMPCAP_OFFSET_MASK;

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

  return v;
}

static int
scalein_vol (int v, unsigned int ampcaps)
{
  int offs, step, range, maxval, origv = v;

  range = ((ampcaps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) + 1;
  step = ((ampcaps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) + 1;
  offs = (ampcaps >> AMPCAP_OFFSET_SHIFT) & AMPCAP_OFFSET_MASK;

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

  return v;
}

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, 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 -EIO;

  if (wid >= mixer->codecs[cad]->nwidgets)
    return -EIO;

  widget = &mixer->codecs[cad]->widgets[wid];

  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 -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 -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 -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 -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 -EIO;
	return (a >> 7) & 0x01;
	break;

      case CT_SELECT:
	return widget->current_selector;
	break;

      case CT_OUTGAINSEL:
	if (!corb_read (mixer, cad, wid, 0, GET_GAIN (1, 1), 0, &a, &b))
	  return -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 -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 -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 -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 -EIO;
	return (a >> 7) & 0x01;
	break;

      default:
	return -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_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=Hi-Z */
	      corb_write (mixer, cad, wid, 0, SET_PINCTL, 0xc0);
	    }
	  else
	    {
	      /* Input select */
	      v = value - widget->nconn;

	      /*
	       * TODO: What are the correct Vref settings for mic and line?
	       */

	      if (v == 1)	/* Line-in */
		{
		  corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x24);	/*Vref=80% */
		}
	      else		/* Mic-in */
		{
		  corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x21);	/*Vref=50% */
		}
	    }
	  return 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);
	  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);
	  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);
	  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);
	  return value;
	  break;

	default:
	  return -EINVAL;
	}
    }

  return -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 = 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 = PIN_UNKNOWN;

	      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)
      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 */
		  /* TODO: Handle mic amp */
		  corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x20);
		}
	      if (widget->pin_type == PIN_MIC)
		{		/* Input PIN (mic) */
		  corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x20);
		}
	      else
		{		/* Output PIN */
		  corb_write (mixer, cad, wid, 0, SET_PINCTL, 0xc0);
		}
	    }
	}

  return 1;
}

static int
hdaudio_mix_init (int dev)
{
  hdaudio_mixer_t *mixer = mixer_devs[dev]->devc;
  char tmp[32];
  int err, g;

  int cad, wid;

  for (cad = 0; cad < mixer->ncodecs; cad++)
    if (mixer->codecs[cad] != NULL)
      {
	codec_t *codec = mixer->codecs[cad];
	int group = 0;

	if (codec == NULL || codec->nwidgets == 0)	/* Codec not active */
	  continue;

	if (mixer->working_codecs > 1)
	  {
	    sprintf (tmp, "codec%d", cad + 1);
	    if ((group = mixer_ext_create_group (dev, 0, tmp)) < 0)
	    {
	      return group;
	    }
	  }

	/*
	 * Enable the new generic mixer driver
	 */
	if (codec->mixer_init == NULL)
	  codec->mixer_init = hdaudio_generic_mixer_init;

	if (codec->mixer_init != NULL)
	{
	  if ((err = codec->mixer_init (dev, mixer, cad, group)) < 0)
	    {
	      if (err != -EAGAIN)
		return err;
	      /*
	       * We got EAGAIN whic means that we should fall
	       * to the old generic mixer.
	       */
	    }
	  else
	    continue;
	}
      }

  if (mixer->client_mixer_init != 0)
    mixer->client_mixer_init (dev);

  return 0;
}

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)
{

  static codec_t NULL_codec = { 0 };	/* TODO: Temporary workaround - to be removed */

  hdaudio_mixer_t *mixer;
  int i, err;
  int ncodecs=0;
  char tmp[128];
  int mixer_dev;

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

  for (i = 0; i < 16; i++)
    if (mixer->codecmask & (1 << i))
      {
	if ((err = hdaudio_mixer_attach (mixer, i)) >= 0)
	  ncodecs++;
      }

  if (ncodecs==0)
  {
	cmn_err(CE_WARN, "No hdaudio codecs were detected\n");
	return NULL;
  }

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

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

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, node_count;
  unsigned int inamp_caps, outamp_caps;
  int group_type, wid_type;
  int i, first_node, num_nodes;

  codec_t *codec = mixer->codecs[cad];
  widget_t *widget;

  if (codec == NULL)
    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, "hdaudio: Too many widgets (%d/%d)\n", wid,
	       MAX_WIDGETS);
      return 0;
    }

  mixer->ncontrols++;

  codec->nwidgets = wid + 1;
  widget = &codec->widgets[wid];

  widget->cad = cad;
  widget->wid = wid;

  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 -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 -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,
		   "hdaudio: Long form connection list not supported\n");
	  return 0;
	}

      if (clen > 0)
	{
	  if (clen > MAX_CONN)
	    {
	      cmn_err (CE_WARN, "hdaudio: 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].refcount++;

		      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 connectionsi(B) for widget %d (%d)\n",
				       widget->wid, widget->nconn);
			      break;
			    }

			  widget->connections[widget->nconn++] = x;
			  codec->widgets[v].refcount++;
			}
		    }
		  else
		    {
		      widget->connections[widget->nconn++] = v;
		      codec->widgets[v].refcount++;
		    }
		}
	    }

	  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, s;
	  hdaudio_endpointinfo_t *endpoint;

	  if (wid_type == 0)
	    {			/* Output endpoint */
	      if (mixer->num_outendpoints >= HDA_MAX_OUTSTREAMS)
		{
		  cmn_err (CE_WARN,
			   "hdaudio: Too many output endpoints (%d)\n",
			   mixer->num_outendpoints);
		  return 0;
		}

	      endpoint = &mixer->outendpoints[mixer->num_outendpoints++];

	      endpoint->ix = mixer->num_outendpoints - 1;

	      s = mixer->num_outendpoints - 1;
	      if (s < 8 && mixer->multich_map != 0
		  && mixer->multich_map != 0x76543210)
		{
		  /*
		   * Reorder the stream numbers so that front stream becomes 1 and
		   * the other streams follow it. In this way it's possible to
		   * get other DACs to play copy of the front stream.
		   */

		  int x;

		  for (x = 0; x < 8; x++)
		    if (s == (((mixer->multich_map) >> (x * 4)) & 0x0f))
		      {
			s = x;
			break;
		      }
		}

	      endpoint->stream_number = endpoint->default_stream_number =
		s + 1;

	      endpoint->iddle_stream = 0;
	    }
	  else
	    {			/* Input endpoint */
	      if (mixer->num_inendpoints >= HDA_MAX_INSTREAMS)
		{
		  cmn_err (CE_WARN, "hdaudio: Too many input endpoints\n");
		  return 0;
		}

	      endpoint = &mixer->inendpoints[mixer->num_inendpoints++];

	      endpoint->stream_number = endpoint->default_stream_number =
		mixer->num_inendpoints;
	      endpoint->ix = mixer->num_inendpoints - 1;
	      endpoint->iddle_stream = 0;
	    }

	  endpoint->cad = cad;
	  endpoint->wid = wid;
	  endpoint->name = widget->name;
	  widget->endpoint = endpoint;

	  if (widget->widget_caps & WCAP_STEREO)
	    endpoint->channels = 2;
	  else
	    endpoint->channels = 1;

#if 0
	  /* This is now handled by hdaudio.c */
	  if (wid_type == NT_DAC)
	    {
	      if (!(widget->widget_caps & WCAP_DIGITAL))	/* Analog */
		if (mixer->num_outendpoints == 1)	/* First DAC */
		  {
		    endpoint->channels = 8;	/* Hope so */
		  }
	    }
#endif

	  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;
	  int num = codec->jack_number++;
	  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)
	    {			/* Plan A */
	      int default_device = (conf >> 20) & 0x0f;
	      int default_loc = (conf >> 24) & 0x3f;

	      int color;
	      char *name = NULL, *loc = "";

	      color = (conf >> 12) & 0x0f;

	      DDB (cmn_err (CE_CONT, "\tConfig default %08x\n", conf));

	      if (default_loc == 8)	/* Front bay */
		loc = "aux-";
	      if (default_loc == 8)	/* Rear panel */
		loc = "rear-";

	      switch (default_device)
		{
		case 0x0:
		  name = "lineout";
		  widget->pin_type = PIN_OUT;
		  break;
		case 0x1:
		  name = "speaker";
		  widget->pin_type = PIN_OUT;
		  break;
		case 0x2:
		  name = "headphone";
		  widget->pin_type = PIN_OUT;
		  break;
		case 0x3:
		  name = "cd";
		  break;
		case 0x4:
		  name = "spdifout";
		  widget->pin_type = PIN_OUT;
		  break;
		case 0x5:
		  name = "digout";
		  widget->pin_type = PIN_OUT;
		  break;
		case 0x6:
		  name = "modem";
		  break;
		case 0x7:
		  name = "phone";
		  break;
		case 0x8:
		  name = "linein";
		  break;
		case 0x9:
		  name = "aux";
		  break;
		case 0xa:
		  name = "mic";
		  break;
		case 0xb:
		  name = "telephony";
		  break;
		case 0xc:
		  name = "spdifin";
		  break;
		case 0xd:
		  name = "digin";
		  break;
		case 0xe:
		  name = "reserved";
		  break;
		case 0xf:	/* Unused pin widget */
		  name = "unused";
		  widget->skip = 1;
		  break;
		}

	      switch (color)
		{
		case 0x1:
		  name = "black";
		  break;
		case 0x2:
		  name = "grey";
		  break;
		case 0x3:
		  name = "blue";
		  break;
		case 0x4:
		  name = "green";
		  break;
		case 0x5:
		  name = "red";
		  break;
		case 0x6:
		  name = "orange";
		  break;
		case 0x7:
		  name = "yellow";
		  break;
		case 0x8:
		  name = "purple";
		  break;
		case 0x9:
		  name = "pink";
		  break;
		case 0xe:
		  name = "white";
		  break;
		}

	      sprintf (widget->name, "%s%s", loc, name);
	      break;

	    }			/* Plan A */
	  else
	    {			/* Plan B */
/*
 * The BIOS has not given any info about this pin/jack. Try to use some default
 * names.
 *
 * TODO: This should be done based on the codec vendor/device ID.
 */
	      switch (num)
		{
		case 0:
		  strcpy (widget->name, "front");
		  widget->pin_type = PIN_OUT;
		  break;
		case 1:
		  strcpy (widget->name, "rear");
		  widget->pin_type = PIN_OUT;
		  break;
		case 2:
		  strcpy (widget->name, "c/l");
		  widget->pin_type = PIN_OUT;
		  break;
		case 3:
		  strcpy (widget->name, "surr");
		  widget->pin_type = PIN_OUT;
		  break;
		case 4:
		  strcpy (widget->name, "mic");
		  break;	/* mic */
		case 5:
		  strcpy (widget->name, "mic");
		  break;	/* mic */
		case 6:
		  strcpy (widget->name, "line");
		  break;	/* line */
		case 7:
		  strcpy (widget->name, "line");
		  break;	/* line */
		case 8:
		  strcpy (widget->name, "cd");
		  break;
		case 9:
		  strcpy (widget->name, "beep");
		  break;
		case 10:
		  strcpy (widget->name, "spdout");
		  widget->pin_type = PIN_OUT;
		  break;
		case 11:
		  strcpy (widget->name, "spdin");
		  break;
		}

	      if (pincaps & (1 << 6))
		cmn_err (CE_WARN, "hdaudio: 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;
	      /* TODO: Handle PIN_MIC */

	      if (widget->widget_caps & WCAP_DIGITAL)	/* Digital */
		{
		  int acc = (pincaps >> 4) & 0x03;

		  switch (acc)
		    {
		    case 1:
		      strcpy (widget->name, "spdout");
		      break;
		    case 2:
		      strcpy (widget->name, "spdin");
		      break;
		    default:
		      strcpy (widget->name, "spdif");
		    }
		  break;
		}
	      break;
	    }			/* Plan B */
	}
	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 (mixer->remap != NULL)
    {
      int w;

      for (w = 0; mixer->remap[w] != NULL; w++)
	if (w == wid)
	  {
	    char *s = mixer->remap[w];

	    if (*s != 0)
	      strcpy (widget->name, s);
	    break;
	  }
    }

  return 1;
}

static int
attach_group (hdaudio_mixer_t * mixer, int cad, int wid, int parent)
{
  unsigned int a, b, gt;
  int i, first_node, num_nodes;
  codec_t *codec = mixer->codecs[cad];

  if (codec == NULL)
    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) != 1)
    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;

  return 1;
}

int
hdaudio_mixer_attach (hdaudio_mixer_t * mixer, int cad)
{
  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, "hdaudio_mixer_attach: Too many codecs %d\n", cad);
      return -EIO;
    }

  mixer->ncodecs = cad + 1;

  if ((codec = PMALLOC (mixer->osdev, sizeof (*codec))) == NULL)
    {
      cmn_err (CE_CONT, "Cannot allocate codec descriptor\n");
      return -ENOMEM;
    }

  memset (codec, 0, sizeof (*codec));

  mixer->codecs[cad] = codec;

  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))
    {
      cmn_err (CE_WARN, "hdaudio_mixer_attach: Codec #%d is not responding\n",
	       cad);
      return -EIO;
    }

/*
 * 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 -EIO;
    }

  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);
/*
 * 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) != 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 */
    {
      cmn_err (CE_NOTE, "Unknown HDA codec 0x%08x\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);
	}
    }

  if (has_audio_group && mixer->chip_name == NULL)
     {
  	mixer->chip_name = codecs[ix].name;

  	mixer->remap = codecs[ix].remap;
  	codec->mixer_init = codecs[ix].mixer_init;
  	mixer->vendor_flags = codecs[ix].flags;
  	mixer->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, "hdaudio: 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 == 0x1)		/* Root node */
	{
	  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));

	      for (subix = 0; subdevices[subix].id != 0; subix++)
		if (subdevices[subix].id == a)
		  {
		    DDB (cmn_err
			 (CE_CONT, "Subdevice known as %s\n",
			  subdevices[subix].name));
		    if (subdevices[subix].remap != NULL)
		      mixer->remap = subdevices[subix].remap;
		    if (subdevices[subix].multich_map != 0)
		      mixer->multich_map = subdevices[subix].multich_map;
		    if (subdevices[subix].flags != 0)
		      mixer->vendor_flags = subdevices[subix].flags;
		  }
	    }
	  break;
	}
    }

  if (mixer->multich_map == 0)
    mixer->multich_map = 0x76543210;	/* Sequential order */

  for (i = first_node; i < first_node + num_nodes; i++)
    {
      if (!attach_group (mixer, cad, i, 0))
         continue;

      if (num_nodes > 1)
    	 mixer->working_codecs++;

      /* power up the AFG! */
      corb_write (mixer, cad, i, 0, SET_POWER_STATE, 0);
    }

  return (has_audio_group) ? 0 : -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->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, "hdaudio_codec: Bad bit size\n");
	  return -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, "hdaudio_codec: Bad format %x\n", fmt);
      return -EIO;
    }

  corb_write (mixer, endpoint->cad, endpoint->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;

  corb_write (mixer, endpoint->cad, endpoint->wid, 0, SET_CONVERTER_FORMAT,
	      tmp);
  corb_write (mixer, endpoint->cad, endpoint->wid, 0, SET_CONVERTER,
	      stream_number << 4);

  if (channels > 2)
    {
      /*
       * Set up the converters for the other stereo pairs
       */

      int n = (channels + 1) / 2;

      for (i = 1; i < n; i++)
	{
	  int ix = (mixer->multich_map >> (i * 4)) & 0x0f;
	  hdaudio_endpointinfo_t *ep;
	  ep = &endpoint[ix];

	  corb_write (mixer, ep->cad, ep->wid, 0, SET_CONVERTER,
		      (stream_number << 4) | (i * 2));
	  corb_write (mixer, ep->cad, ep->wid, 0, SET_CONVERTER_FORMAT, tmp);
	}
    }

#if 1
  if (mixer->codecs[endpoint->cad]->codec_desc->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)
	{
	  int x = 0;
	  unsigned int v, b;

	  for (i = 0; x < mixer->num_outendpoints; x++)
	    if ((((mixer->multich_map) >> (x * 4)) & 0x0f) == 0)
	      break;
	  hdaudio_codec_setup_endpoint (mixer, &mixer->outendpoints[x], 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).
   */

  for (i = 0; i < n; i++)
    {
      int ix = (mixer->multich_map >> (i * 4)) & 0x0f;
      hdaudio_endpointinfo_t *ep;

      ep = &endpoint[ix];

      corb_write (mixer, ep->cad, ep->wid, 0, SET_CONVERTER,
		  ep->iddle_stream << 4);
    }

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

void
hda_codec_unsol (hdaudio_mixer_t * mixer, unsigned int upper,
		 unsigned int lower)
{
  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 = &mixer->codecs[name->cad]->widgets[name->wid];
  strcpy (name->name, widget->name);

  return 0;
}

int
hdaudio_codec_audio_ioctl (hdaudio_mixer_t * mixer,
			   hdaudio_endpointinfo_t * endpoint,
			   unsigned int cmd, ioctl_arg arg)
{
  widget_t *widget = &mixer->codecs[endpoint->cad]->widgets[endpoint->wid];
  char tmp[128], *t;
  unsigned int a, b;
  int i, v, left, right;
  int nsteps;


  switch (cmd)
    {
    case SNDCTL_DSP_GET_RECSRC_NAMES:
      *tmp = 0;
      t = tmp;

      for (i = 0; i < widget->nconn; i++)
	{
	  if (*tmp)		/* Not empty */
	    *t++ = ' ';
	  strcpy (t,
		  mixer->codecs[widget->cad]->widgets[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, widget->cad, widget->wid, 0, GET_SELECTOR, 0, &a, &b))
	return -EIO;
      return *arg = a;
      break;

    case SNDCTL_DSP_SET_RECSRC:
      a = *arg;
      if (a > widget->nconn)
	return -EIO;

      corb_write (mixer, widget->cad, widget->wid, 0, SET_SELECTOR, a);
      widget->current_selector = a;
      mixer_devs[mixer->mixer_dev]->modify_counter++;
      return *arg = a;
      break;

    case SNDCTL_DSP_GETRECVOL:
      nsteps = (widget->inamp_caps >> 8) & 0x3f;
      if (nsteps < 1)
	nsteps = 1;
      if (!corb_read
	  (mixer, widget->cad, widget->wid, 0, GET_GAIN (0, 1), 0, &a, &b))
	return -EIO;
      if (a & 0x80)		/* Muted */
	left = 0;
      else
	left = ((a & 0x7f) * 100) / nsteps;
      if (!corb_read
	  (mixer, widget->cad, widget->wid, 0, GET_GAIN (0, 0), 0, &a, &b))
	return -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 = (widget->inamp_caps >> 8) & 0x3f;
      if (nsteps < 1)
	nsteps = 1;

      a = (left * nsteps) / 100;
      if (left == 0)
	a |= 0x80;		/* Mute */
      corb_write (mixer, widget->cad, widget->wid, 0,
		  SET_GAIN (0, 1, 1, 0, 0), a);
      a = (right * nsteps) / 100;
      if (right == 0)
	a |= 0x80;		/* Mute */
      corb_write (mixer, widget->cad, 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 = (widget->inamp_caps >> 8) & 0x3f;
      if (nsteps < 1)
	nsteps = 1;
      if (!corb_read
	  (mixer, widget->cad, widget->wid, 0, GET_GAIN (1, 1), 0, &a, &b))
	return -EIO;
      if (a & 0x80)		/* Muted */
	left = 0;
      else
	left = ((a & 0x7f) * 100) / nsteps;
      if (!corb_read
	  (mixer, widget->cad, widget->wid, 0, GET_GAIN (1, 0), 0, &a, &b))
	return -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 = (widget->inamp_caps >> 8) & 0x3f;
      if (nsteps < 1)
	nsteps = 1;

      a = (left * nsteps) / 100;
      if (left == 0)
	a |= 0x80;		/* Mute */
      corb_write (mixer, widget->cad, widget->wid, 0,
		  SET_GAIN (1, 0, 1, 0, 0), a);
      a = (right * nsteps) / 100;
      if (right == 0)
	a |= 0x80;		/* Mute */
      corb_write (mixer, widget->cad, widget->wid, 0,
		  SET_GAIN (1, 0, 0, 1, 1), a);

      mixer_devs[mixer->mixer_dev]->modify_counter++;
      return *arg = v;
      break;
    }

  return -EINVAL;
}

unsigned int
hdaudio_codec_get_output_mapping (hdaudio_mixer_t * mixer)
{
  return mixer->multich_map;
}
