Google
 

Open Sound System
The Hitchhiker's Guide to OSS 4.1 Internals

Do you have problems with sound/audio application development? Don't panic! Click here for help!

oss_hdaudio/oss_hdaudio.c

The High Definition Audio (HDA/Azalia) driver.

Description




This file is part of Open Sound System.

Copyright (C) 4Front Technologies 1996-2008.

This this source file is released under GPL v2 license (no other versions). See the COPYING file included in the main directory of this source distribution for the license terms and conditions.


#include "oss_hdaudio_cfg.h"
#include "oss_pci.h"
#include "hdaudio.h"
#include "hdaudio_codec.h"
#include "spdif.h"

#define CORB_DELAY 10
#define CORB_LOOPS 1000

#define INTEL_VENDOR_ID         0x8086
#define INTEL_DEVICE_ICH6       0x2668
#define INTEL_DEVICE_ICH7       0x27d8
#define INTEL_DEVICE_ESB2       0x269a
#define INTEL_DEVICE_ICH8       0x284b
#define INTEL_DEVICE_ICH9       0x293f
#define INTEL_DEVICE_ICH10	0x3a3e
#define INTEL_DEVICE_ICH10_B	0x3a6e
#define INTEL_DEVICE_PCH	0x3b56
#define INTEL_DEVICE_P35        0x293e

#define NVIDIA_VENDOR_ID        0x10de
#define NVIDIA_DEVICE_MCP51     0x026c
#define NVIDIA_DEVICE_MCP55     0x0371
#define NVIDIA_DEVICE_MCP61     0x03e4
#define NVIDIA_DEVICE_MCP61A    0x03f0
#define NVIDIA_DEVICE_MCP65     0x044a
#define NVIDIA_DEVICE_MCP67     0x055c
#define NVIDIA_DEVICE_MCP73     0x07fc
#define NVIDIA_DEVICE_MCP78S    0x0774
#define NVIDIA_DEVICE_MCP79     0x0ac0

#define ATI_VENDOR_ID           0x1002
#define ATI_DEVICE_SB450        0x437b
#define ATI_DEVICE_SB600        0x4383

#define VIA_VENDOR_ID           0x1106
#define VIA_DEVICE_HDA          0x3288

#define SIS_VENDOR_ID           0x1039
#define SIS_DEVICE_HDA          0x7502

#define ULI_VENDOR_ID           0x10b9
#define ULI_DEVICE_HDA          0x5461

#define CREATIVE_ID		0x1102
#define CREATIVE_XFI_HDA	0x0009

#define BDL_SIZE	32
#define HDA_MAX_ENGINES	8
#define MAX_OUTPUTS 	8
#define MAX_INPUTS	4

typedef struct
{
  oss_uint64_t addr;
  unsigned int len;
  unsigned int ioc;
} bdl_t;

typedef struct hda_portc_t hda_portc_t;

typedef struct
{
  int num;
  int busy;
  bdl_t *bdl;
  oss_uint64_t bdl_phys;
  oss_dma_handle_t bdl_dma_handle;
  int bdl_size, bdl_max;
  unsigned char *base;
  unsigned int intrmask;
  hda_portc_t *portc;
} hda_engine_t;

struct hda_portc_t
{
  int num;
  int open_mode;
  int speed, bits, channels;
  int audio_enabled;
  int trigger_bits;
  int audiodev;
  int port_type;
#define PT_OUTPUT	0
#define PT_INPUT	1
  hdaudio_endpointinfo_t *endpoint;
  hda_engine_t *engine;
};

typedef struct
{
  unsigned int response, resp_ex;
} rirb_entry_t;

typedef struct hda_devc_t
{
  oss_device_t *osdev;
  oss_native_word base;
  unsigned int membar_addr;
  unsigned char *azbar;

  char *chip_name;
  unsigned int vendor_id, subvendor_id;

  int irq;
  oss_mutex_t mutex;
  oss_mutex_t low_mutex;

  /* CORB and RIRB */
  int corbsize, rirbsize;
  unsigned int *corb;
  rirb_entry_t *rirb;
  oss_uint64_t corb_phys, rirb_phys;
  int rirb_rp;
  unsigned int rirb_upper, rirb_lower;
  volatile int rirb_empty;

  oss_dma_handle_t corb_dma_handle;

  /* Mixer */
  unsigned short codecmask;
  hdaudio_mixer_t *mixer;
  int mixer_dev;
  spdif_devc spdc;

  /* Audio */
  int first_dev;
  hda_engine_t inengines[HDA_MAX_ENGINES];
  hda_engine_t outengines[HDA_MAX_ENGINES];
  int num_outengines, num_inengines;
  hdaudio_endpointinfo_t *spdifout_endpoint;

  hda_portc_t output_portc[MAX_OUTPUTS];
  hda_portc_t input_portc[MAX_INPUTS];
  int num_outputs, num_inputs;

  int num_spdin, num_spdout;
  int num_mdmin, num_mdmout;
}
hda_devc_t;

static int
rirb_intr (hda_devc_t * devc)
{
  int serviced = 0;
  unsigned char rirbsts;
  oss_native_word flags;

  rirbsts = PCI_READB (devc->osdev, devc->azbar + HDA_RIRBSTS);
  if (rirbsts != 0)
    {
      serviced = 1;

      if (rirbsts & 0x01)
	{
	  /* RIRB response interrupt */
	  int wp, rp;
	  unsigned int upper = 0, lower = 0;

	  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
	  wp = PCI_READB (devc->osdev, devc->azbar + HDA_RIRBWP) & 0x00ff;
	  while (devc->rirb_rp != wp)
	    {
	      devc->rirb_rp++;
	      devc->rirb_rp %= devc->rirbsize;
	      rp = devc->rirb_rp;
	      upper = devc->rirb[rp].response;
	      lower = devc->rirb[rp].resp_ex;

	      if (lower & 0x10)	/* Unsolicited response */
		{
		  hda_codec_unsol (devc->mixer, upper, lower);
		}
	      else if (devc->rirb_empty)
		{
		  devc->rirb_upper = upper;
		  devc->rirb_lower = lower;
		  devc->rirb_empty--;
		}
	    }
	  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
	}
      PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBSTS, rirbsts);
    }

  return serviced;
}

static int
hdaintr (oss_device_t * osdev)
{
  hda_devc_t *devc = (hda_devc_t *) osdev->devc;
  unsigned int status;
  int serviced = 0;
  int i;

  if (devc == NULL)		/* Too bad */
    return 1;

  status = PCI_READL (devc->osdev, devc->azbar + HDA_INTSTS);
  if (status != 0)
    {
      serviced = 1;

      for (i = 0; i < devc->num_outengines; i++)
	{
	  hda_engine_t *engine = &devc->outengines[i];

	  hda_portc_t *portc;
	  if (status & engine->intrmask)
	    {
	      PCI_WRITEB (devc->osdev, engine->base + 0x03, 0x1e);

	      portc = engine->portc;

	      if (portc != NULL && (portc->trigger_bits & PCM_ENABLE_OUTPUT))
		oss_audio_outputintr (portc->audiodev, 1);
	    }
	}

      for (i = 0; i < devc->num_inengines; i++)
	{
	  hda_engine_t *engine = &devc->inengines[i];

	  hda_portc_t *portc;
	  if (status & engine->intrmask)
	    {
	      PCI_WRITEB (devc->osdev, engine->base + 0x03, 0x1e);

	      portc = engine->portc;

	      if (portc != NULL && (portc->trigger_bits & PCM_ENABLE_INPUT))
		oss_audio_inputintr (portc->audiodev, 0);
	    }
	}
      PCI_WRITEL (devc->osdev, devc->azbar + HDA_INTSTS, status);	/* ACK */

      if (status & (1 << 30))	/* Controller interrupt (RIRB) */
	{
	  if (rirb_intr (devc))
	    serviced = 1;
	}
    }
  return serviced;
}

static int
do_corb_write (void *dc, unsigned int cad, unsigned int nid, unsigned int d,
	       unsigned int verb, unsigned int parm)
{
  unsigned int wp;
  unsigned int tmp;
  oss_native_word flags;
  hda_devc_t *devc = (hda_devc_t *) dc;

  tmp = (cad << 28) | (d << 27) | (nid << 20) | (verb << 8) | (parm & 0xffff);
  wp = PCI_READB (devc->osdev, devc->azbar + HDA_CORBWP) & 0x00ff;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  wp = (wp + 1) % devc->corbsize;

  devc->corb[wp] = tmp;
  devc->rirb_empty++;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_CORBWP, wp);

  return 1;
}

static int
do_corb_write_nomutex (void *dc, unsigned int cad, unsigned int nid, unsigned int d,
	       unsigned int verb, unsigned int parm)
{
  unsigned int wp;
  unsigned int tmp;
  hda_devc_t *devc = (hda_devc_t *) dc;

  tmp = (cad << 28) | (d << 27) | (nid << 20) | (verb << 8) | (parm & 0xffff);
  wp = PCI_READB (devc->osdev, devc->azbar + HDA_CORBWP) & 0x00ff;

  wp = (wp + 1) % devc->corbsize;

  devc->corb[wp] = tmp;
  devc->rirb_empty++;
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_CORBWP, wp);

  return 1;
}

static int
do_corb_read (void *dc, unsigned int cad, unsigned int nid, unsigned int d,
	      unsigned int verb, unsigned int parm, unsigned int *upper,
	      unsigned int *lower)
{
  int tmout;
  hda_devc_t *devc = (hda_devc_t *) dc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  do_corb_write_nomutex (devc, cad, nid, d, verb, parm);

  tmout = CORB_LOOPS;
  while (devc->rirb_empty)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

      if (--tmout < 0)
	{
	  devc->rirb_rp = PCI_READB (devc->osdev, devc->azbar + HDA_RIRBWP);
	  devc->rirb_empty = 0;
	  return 0;
	}
      oss_udelay (CORB_DELAY);
      MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
    }

  *upper = devc->rirb_upper;
  *lower = devc->rirb_lower;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 1;
}

static int
do_corb_read_poll (void *dc, unsigned int cad, unsigned int nid,
		   unsigned int d, unsigned int verb, unsigned int parm,
		   unsigned int *upper, unsigned int *lower)
{
  int tmout = 0;
  hda_devc_t *devc = (hda_devc_t *) dc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  do_corb_write_nomutex (devc, cad, nid, d, verb, parm);

  tmout = CORB_LOOPS;
  while (devc->rirb_empty)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

      rirb_intr (devc);

      if (--tmout < 0)
	{
	  devc->rirb_rp = PCI_READB (devc->osdev, devc->azbar + HDA_RIRBWP);
	  devc->rirb_empty = 0;
	  return 0;
	}
      oss_udelay (CORB_DELAY);
      MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
    }

  *upper = devc->rirb_upper;
  *lower = devc->rirb_lower;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 1;
}

#undef corb_read

static int
corb_read (void *dc, unsigned int cad, unsigned int nid, unsigned int d,
	   unsigned int verb, unsigned int parm, unsigned int *upper,
	   unsigned int *lower)
{
  hda_devc_t *devc = (hda_devc_t *) dc;


Do three retries using different access methods

  if (do_corb_read (dc, cad, nid, d, verb, parm, upper, lower))
    return 1;

  PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBCTL, 0x02);	/* Intr disable */

  if (do_corb_read_poll (dc, cad, nid, d, verb, parm, upper, lower))
    {
      PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBCTL, 0x03);	/* Intr re-enable */
      return 1;
    }

  if (do_corb_read_poll (dc, cad, nid, d, verb, parm, upper, lower))
    {
      PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBCTL, 0x03);	/* Intr re-enable */
      return 1;
    }

#if 0
  // TODO: Implement this
  if (do_corb_read_single (dc, cad, nid, d, verb, parm, upper, lower))
    {
      PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBCTL, 0x03);	/* Intr re-enable */
      return 1;
    }
#endif

  PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBCTL, 0x03);	/* Intr re-enable */

  cmn_err (CE_WARN,
	   "RIRB timeout (cad=%d, nid=%d, d=%d, verb=%03x, parm=%x)\n",
	   cad, nid, d, verb, parm);

  return 0;
}


Audio routines


static int
hda_audio_set_rate (int dev, int arg)
{
  hda_portc_t *portc = audio_engines[dev]->portc;
  adev_p adev = audio_engines[dev];

  int best = -1, bestdiff = 10000000;
  int i;

  if (arg == 0)
    return portc->speed;

  for (i = 0; i < adev->nrates; i++)
    {
      int diff = arg - adev->rates[i];
      if (diff < 0)
	diff = -diff;

      if (diff < bestdiff)
	{
	  best = i;
	  bestdiff = diff;
	}
    }

  if (best == -1)
    {
      cmn_err (CE_WARN, "No suitable rate found!\n");
      return portc->speed = 48000;	/* Some default */
    }
  portc->speed = adev->rates[best];
  return portc->speed;
}

static short
hda_audio_set_channels (int dev, short arg)
{
  hda_portc_t *portc = audio_engines[dev]->portc;
  hda_devc_t *devc = audio_engines[dev]->devc;
  adev_p adev = audio_engines[dev];
  int i, n1, n2;

  if (arg == 0)
    {
      return portc->channels;
    }

  if (arg < 1)
    arg = 1;

  if (arg > adev->max_channels)
  {
    arg = adev->max_channels;
  }

  if (arg != 1 && arg != 2 && arg != 4 && arg != 6 && arg != 8)
    {
      cmn_err (CE_NOTE, "Ignored request for odd number (%d) of channels\n", arg);
      return portc->channels = arg & ~1;
    }

  if (arg < adev->min_channels)
    arg = adev->min_channels;


Check if more output endpoints need to be allocated

  n2 = (arg + 1) / 2;
  n1 = (portc->channels + 1) / 2;
  if (n1 < 1)
    n1 = 1;

  if (n2 > n1)			/* Needs more stereo pairs */
    {
      oss_native_word flags;

      MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

      for (i = n1; i < n2; i++)
	{
	  if (portc->endpoint[i].busy)
	    {
	      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
	      return portc->channels;
	    }
	}

      for (i = n1; i < n2; i++)
	{
	  portc->endpoint[i].busy = 1;
	  portc->endpoint->borrow_count++;
	}

      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
    }
  else if (n2 < n1)		/* Some stereo pairs can be released */
    {
      for (i = n2; i < n1; i++)
	{
	  portc->endpoint[i].busy = 0;
	  portc->endpoint->borrow_count--;
	}
    }

  return portc->channels = arg;
}

static unsigned int
hda_audio_set_format (int dev, unsigned int arg)
{
  hda_portc_t *portc = audio_engines[dev]->portc;

  if (arg == 0)
    return portc->bits;

  if (!(arg & audio_engines[dev]->oformat_mask))
    return portc->bits;
  portc->bits = arg;

  return portc->bits;
}

static int
do_corb_write_simple (void *dc, unsigned int v)
{
  unsigned int wp;
  hda_devc_t *devc = (hda_devc_t *) dc;

  wp = PCI_READB (devc->osdev, devc->azbar + HDA_CORBWP) & 0x00ff;

  wp = (wp + 1) % devc->corbsize;

  devc->corb[wp] = v;
  devc->rirb_empty++;
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_CORBWP, wp);

  return 1;
}

static int
do_corb_read_simple (void *dc, unsigned int cmd, unsigned int *v)
{
  int tmout = 0;
  hda_devc_t *devc = (hda_devc_t *) dc;

  do_corb_write_simple (dc, cmd);

  tmout = CORB_LOOPS;
  while (devc->rirb_empty)
    {
      if (--tmout < 0)
	{
	  cmn_err (CE_WARN, "RIRB timeout (cmd=%08x)\n", cmd);
	  devc->rirb_rp = PCI_READB (devc->osdev, devc->azbar + HDA_RIRBWP);
	  devc->rirb_empty = 0;
	  return 0;
	}
      oss_udelay (CORB_DELAY);
    }

  *v = devc->rirb_upper;

  return 1;
}

static int
hda_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  hda_devc_t *devc = audio_engines[dev]->devc;
  hda_portc_t *portc = audio_engines[dev]->portc;

  //if (hdaudio_snoopy)
    switch (cmd)
      {
      case HDA_IOCTL_WRITE:
#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif
	do_corb_write_simple (devc, *(unsigned int *) arg);
	return 0;
	break;

      case HDA_IOCTL_READ:
#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif
	if (!do_corb_read_simple
	    (devc, *(unsigned int *) arg, (unsigned int *) arg))
	  return OSS_EIO;

	return 0;
	break;

      case HDA_IOCTL_NAME:
#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif
	return hda_codec_getname (devc->mixer, (hda_name_t *) arg);
	break;

      case HDA_IOCTL_WIDGET:
#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif
	return hda_codec_getwidget (devc->mixer, (hda_widget_info_t *) arg);
	break;

      }
  return hdaudio_codec_audio_ioctl (devc->mixer, portc->endpoint, cmd, arg);
}

static void hda_audio_trigger (int dev, int state);

static void
hda_audio_reset (int dev)
{
  hda_audio_trigger (dev, 0);
}

/*ARGSUSED*/
static int
hda_audio_open (int dev, int mode, int openflags)
{
  hda_portc_t *portc = audio_engines[dev]->portc;
  hda_devc_t *devc = audio_engines[dev]->devc;
  oss_native_word flags;
  hda_engine_t *engines, *engine = NULL;
  int i, n;
  unsigned int tmp;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  if (portc->open_mode || portc->endpoint->busy)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return OSS_EBUSY;
    }

  if (portc->port_type == PT_INPUT)
    {
      engines = devc->inengines;
      n = devc->num_inengines;
    }
  else
    {
      engines = devc->outengines;
      n = devc->num_outengines;
    }

  for (i = 0; i < n && engine == NULL; i++)
    {
      if (!engines[i].busy)
	engine = &engines[i];
    }

  if (engine == NULL)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      cmn_err (CE_WARN, "No free DMA engines.\nn");
      return OSS_EBUSY;
    }

  portc->open_mode = mode;
  portc->audio_enabled &= ~mode;
  portc->endpoint->busy = 1;
  portc->endpoint->borrow_count = 1;

  portc->engine = engine;
  engine->portc = portc;
  portc->engine->busy = 1;

  portc->endpoint->stream_number = portc->endpoint->default_stream_number;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);


Reset the DMA engine and wait for the reset to complete

  tmp = PCI_READL (devc->osdev, engine->base);
  PCI_WRITEL (devc->osdev, engine->base, tmp | 0x01);	/* Stream reset */

  for (i = 0; i < 1000; i++)
    {
      if (PCI_READL (devc->osdev, engine->base) & 0x01)
	break;
      oss_udelay (1000);
    }

  /* Unreset and wait */

  tmp = PCI_READL (devc->osdev, engine->base);
  PCI_WRITEL (devc->osdev, engine->base, tmp & ~0x01);	/* Release reset */

  for (i = 0; i < 1000; i++)
    {
      if (!(PCI_READL (devc->osdev, engine->base) & 0x01))
	break;
      oss_udelay (1000);
    }

  return 0;
}

static void
hda_audio_close (int dev, int mode)
{
  hda_portc_t *portc = audio_engines[dev]->portc;
  hda_devc_t *devc = audio_engines[dev]->devc;
  oss_native_word flags;
  int i;

  if (!portc->open_mode)
    {
      cmn_err (CE_WARN, "Bad close %d\n", dev);
      return;
    }

  hda_audio_reset (dev);
  hdaudio_codec_reset_endpoint (devc->mixer, portc->endpoint,
				portc->channels);

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  portc->audio_enabled &= ~mode;
  portc->engine->busy = 0;
  portc->engine->portc = NULL;
  portc->engine = NULL;
  portc->endpoint->busy = 0;
  portc->endpoint->stream_number = portc->endpoint->default_stream_number;

  for (i = 1; i < portc->endpoint->borrow_count; i++)
    {
      portc->endpoint[i].busy = 0;
    }

  portc->endpoint->borrow_count = 1;
  portc->open_mode = 0;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

/*ARGSUSED*/
static void
hda_audio_output_block (int dev, oss_native_word buf, int count,
			int fragsize, int intrflag)
{
  hda_portc_t *portc = audio_engines[dev]->portc;

  portc->audio_enabled |= PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
}

/*ARGSUSED*/
static void
hda_audio_start_input (int dev, oss_native_word buf, int count,
		       int fragsize, int intrflag)
{
  hda_portc_t *portc = audio_engines[dev]->portc;

  portc->audio_enabled |= PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;
}

static void
hda_audio_trigger (int dev, int state)
{
  hda_devc_t *devc = audio_engines[dev]->devc;
  hda_portc_t *portc = audio_engines[dev]->portc;
  hda_engine_t *engine = portc->engine;
  oss_native_word flags;
  unsigned char tmp, intr;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  tmp = PCI_READB (devc->osdev, engine->base + 0x00);
  intr = PCI_READB (devc->osdev, devc->azbar + HDA_INTCTL);
  if (portc->open_mode & OPEN_WRITE)
    {
      if (state & PCM_ENABLE_OUTPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      portc->trigger_bits |= PCM_ENABLE_OUTPUT;
	      tmp |= 0x1e;	/* DMA and Stream Interrupt enable */
	      intr |= engine->intrmask;
	      PCI_WRITEB (devc->osdev, engine->base + 0x00, tmp);
	      PCI_WRITEB (devc->osdev, devc->azbar + HDA_INTCTL, intr);
	    }
	}
      else
	{
	  if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
	      portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
	      tmp &= ~0x1e;	/* Run-off & intr disable */
	      intr &= ~engine->intrmask;
	      /* Stop DMA and Stream Int */
	      PCI_WRITEB (devc->osdev, engine->base + 0x00, tmp);
	      /* Ack pending ints */
	      PCI_WRITEB (devc->osdev, engine->base + 0x03, 0x1c);
	      /* Disable Interrupts */
	      PCI_WRITEB (devc->osdev, devc->azbar + HDA_INTCTL, intr);
	    }
	}
    }

  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      portc->trigger_bits |= PCM_ENABLE_INPUT;
	      tmp |= 0x1e;	/* dma & intr enable */
	      intr |= engine->intrmask;
	      PCI_WRITEB (devc->osdev, engine->base + 0x00, tmp);
	      PCI_WRITEB (devc->osdev, devc->azbar + HDA_INTCTL, intr);
	    }
	}
      else
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      portc->audio_enabled &= ~PCM_ENABLE_INPUT;
	      portc->trigger_bits &= ~PCM_ENABLE_INPUT;
	      tmp &= ~0x1e;	/* Run-off & intr disable */
	      intr &= ~engine->intrmask;
	      /* Stop DMA and Stream Int */
	      PCI_WRITEB (devc->osdev, engine->base + 0x00, tmp);
	      /* Ack pending ints */
	      PCI_WRITEB (devc->osdev, engine->base + 0x03, 0x1c);
	      /* Disable Interrupts */
	      PCI_WRITEB (devc->osdev, devc->azbar + HDA_INTCTL, intr);
	    }
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

static void
init_bdl (hda_devc_t * devc, hda_engine_t * engine, dmap_p dmap)
{
  int i;
  bdl_t *bdl = engine->bdl;

  PCI_WRITEL (devc->osdev, engine->base + HDA_SD_BDLPL, 0);
  PCI_WRITEL (devc->osdev, engine->base + HDA_SD_BDLPU, 0);
  for (i = 0; i < dmap->nfrags; i++)
    {
      bdl[i].addr = dmap->dmabuf_phys + (i * dmap->fragment_size);
      bdl[i].len = dmap->fragment_size;
      bdl[i].ioc = 0x01;
    }
}

static int
setup_audio_engine (hda_devc_t * devc, hda_engine_t * engine, hda_portc_t * portc,
		    dmap_t * dmap)
{
  unsigned int tmp, tmout;

/* make sure the run bit is zero for SD */
  PCI_WRITEB (devc->osdev, engine->base + HDA_SD_CTL,
	      PCI_READB (devc->osdev, engine->base + HDA_SD_CTL) & ~0x2);

First reset the engine.


  tmp = PCI_READB (devc->osdev, engine->base + HDA_SD_CTL);
  PCI_WRITEB (devc->osdev, engine->base + HDA_SD_CTL, tmp | 0x01);	/* Reset */
  oss_udelay (1000);

  tmout = 300;
  while (!(PCI_READB (devc->osdev, engine->base + HDA_SD_CTL) & 0x01)
	 && --tmout)
    oss_udelay (1000);

  PCI_WRITEB (devc->osdev, engine->base + HDA_SD_CTL, tmp & ~0x01);	/* Release reset */
  oss_udelay (1000);
  tmout = 300;
  while ((PCI_READB (devc->osdev, engine->base + HDA_SD_CTL) & 0x01)
	 && --tmout)
    oss_udelay (1000);


Set the engine tag number field

  tmp = PCI_READL (devc->osdev, engine->base + HDA_SD_CTL);
  tmp &= ~(0xf << 20);
  tmp |= portc->endpoint->stream_number << 20;
  PCI_WRITEL (devc->osdev, engine->base + HDA_SD_CTL, tmp);

/* program buffer size and fragments in the engine */

  PCI_WRITEL (devc->osdev, engine->base + HDA_SD_CBL, dmap->bytes_in_use);
  PCI_WRITEW (devc->osdev, engine->base + HDA_SD_LVI, dmap->nfrags - 1);

/* setup the BDL base address */
  tmp = engine->bdl_phys;
  PCI_WRITEL (devc->osdev, engine->base + HDA_SD_BDLPL, tmp & 0xffffffff);
  tmp = engine->bdl_phys >> 32;
  PCI_WRITEL (devc->osdev, engine->base + HDA_SD_BDLPU, tmp & 0xffffffff);
  PCI_WRITEB (devc->osdev, engine->base + HDA_SD_STS, 0x1c);	/* mask out ints */


Next the sample rate and format setup

  if (hdaudio_codec_setup_endpoint
      (devc->mixer, portc->endpoint, portc->speed, portc->channels,
       portc->bits, portc->endpoint->stream_number, &tmp) < 0)
    {
      cmn_err (CE_WARN, "Codec setup failed\n");
      return OSS_EIO;
    }

  PCI_WRITEW (devc->osdev, engine->base + HDA_SD_FORMAT, tmp);
  tmp = PCI_READW (devc->osdev, engine->base + HDA_SD_FORMAT);

  return 0;
}

/*ARGSUSED*/
static int
hda_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  hda_devc_t *devc = audio_engines[dev]->devc;
  hda_portc_t *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  oss_native_word flags;
  hda_engine_t *engine;

  if (portc->port_type != PT_INPUT)
    return OSS_ENOTSUP;

  engine = portc->engine;

  if (dmap->nfrags > engine->bdl_max)	/* Overflow protection */
    {
      dmap->nfrags = engine->bdl_max;
      dmap->bytes_in_use = dmap->nfrags * dmap->fragment_size;
    }

  init_bdl (devc, engine, dmap);

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  portc->audio_enabled &= ~PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return setup_audio_engine (devc, engine, portc, dmap);
}

/*ARGSUSED*/
static int
hda_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  hda_devc_t *devc = audio_engines[dev]->devc;
  hda_portc_t *portc = audio_engines[dev]->portc;
  hda_engine_t *engine;

  dmap_t *dmap = audio_engines[dev]->dmap_out;
  oss_native_word flags;

  if (portc->port_type != PT_OUTPUT)
    return OSS_ENOTSUP;

  engine = portc->engine;

  if (dmap->nfrags > engine->bdl_max)	/* Overflow protection */
    {
      dmap->nfrags = engine->bdl_max;
      dmap->bytes_in_use = dmap->nfrags * dmap->fragment_size;
    }

  init_bdl (devc, engine, dmap);

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return setup_audio_engine (devc, engine, portc, dmap);
}

/*ARGSUSED*/
static int
hda_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  hda_portc_t *portc = audio_engines[dev]->portc;
#ifdef sun
  // PCI_READL under solaris needs devc->osdev
  hda_devc_t *devc = audio_engines[dev]->devc;
#endif
  hda_engine_t *engine;
  unsigned int ptr;

  engine = portc->engine;
  ptr =
    PCI_READL (devc->osdev, engine->base + HDA_SD_LPIB) % dmap->bytes_in_use;

  return ptr;
}

static const audiodrv_t hda_audio_driver = {
  hda_audio_open,
  hda_audio_close,
  hda_audio_output_block,
  hda_audio_start_input,
  hda_audio_ioctl,
  hda_audio_prepare_for_input,
  hda_audio_prepare_for_output,
  hda_audio_reset,
  NULL,
  NULL,
  NULL,
  NULL,
  hda_audio_trigger,
  hda_audio_set_rate,
  hda_audio_set_format,
  hda_audio_set_channels,
  NULL,
  NULL,
  NULL,				/* hda_check_input, */
  NULL,				/* hda_check_output, */
  NULL,				/* hda_alloc_buffer */
  NULL,				/* hda_free_buffer */
  NULL,
  NULL,
  hda_get_buffer_pointer
};

static int
reset_controller (hda_devc_t * devc)
{
  unsigned int tmp, tmout;

  tmp = PCI_READL (devc->osdev, devc->azbar + HDA_GCTL);
  tmp &= ~CRST;
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_GCTL, tmp);

  tmout = 50;
  while ((PCI_READL (devc->osdev, devc->azbar + HDA_GCTL) & CRST) && --tmout)
    oss_udelay (1000);

  oss_udelay (1000);

  tmp = PCI_READL (devc->osdev, devc->azbar + HDA_GCTL);
  tmp |= CRST;
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_GCTL, tmp);

  tmout = 50;
  while (!(PCI_READL (devc->osdev, devc->azbar + HDA_GCTL) & CRST) && --tmout)
    oss_udelay (1000);

  oss_udelay (1000);

  if (!(PCI_READL (devc->osdev, devc->azbar + HDA_GCTL)))
    {
      cmn_err (CE_WARN, "Controller not ready\n");
      return 0;
    }

  if (!devc->codecmask)
    {
      devc->codecmask = PCI_READW (devc->osdev, devc->azbar + HDA_STATESTS);
      DDB (cmn_err (CE_CONT, "Codec mask %x\n", devc->codecmask));
    }

  return 1;
}

static int
setup_controller (hda_devc_t * devc)
{
  unsigned int tmp, tmout;
  oss_native_word phaddr;


Allocate the CORB and RIRB buffers.

  tmp = PCI_READB (devc->osdev, devc->azbar + HDA_CORBSIZE) & 0x03;

  switch (tmp)
    {
    case 0:
      devc->corbsize = 2;
      break;
    case 1:
      devc->corbsize = 16;
      break;
    case 2:
      devc->corbsize = 256;
      break;
    default:
      cmn_err (CE_WARN, "Bad CORB size\n");
      return 0;
    }

  tmp = PCI_READB (devc->osdev, devc->azbar + HDA_RIRBSIZE) & 0x03;

  switch (tmp)
    {
    case 0:
      devc->rirbsize = 2;
      break;
    case 1:
      devc->rirbsize = 16;
      break;
    case 2:
      devc->rirbsize = 256;
      break;
    default:
      cmn_err (CE_WARN, "Bad CORB size\n");
      return 0;
    }

  if ((devc->corb =
       CONTIG_MALLOC (devc->osdev, 4096, MEMLIMIT_32BITS, &phaddr, devc->corb_dma_handle)) == NULL)
    {
      cmn_err (CE_WARN, "Out of memory (CORB)\n");
      return 0;
    }

  devc->corb_phys = phaddr;

  devc->rirb = (rirb_entry_t *) (devc->corb + 512);	/* 512 dwords = 2048 bytes */
  devc->rirb_phys = devc->corb_phys + 2048;


Initialize CORB registers


  PCI_WRITEB (devc->osdev, devc->azbar + HDA_CORBCTL, 0);	/* Stop */

  tmout = 0;

  while (tmout++ < 500
	 && (PCI_READB (devc->osdev, devc->azbar + HDA_CORBCTL) & 0x02));

  if (PCI_READB (devc->osdev, devc->azbar + HDA_CORBCTL) & 0x02)
    {
      cmn_err (CE_WARN, "CORB didn't stop\n");
    }

  tmp = devc->corb_phys & 0xffffffff;
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_CORBLBASE, tmp);
  tmp = (devc->corb_phys >> 32) & 0xffffffff;
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_CORBUBASE, tmp);

  PCI_WRITEW (devc->osdev, devc->azbar + HDA_CORBWP, 0x0);	/* Reset to 0 */
  PCI_WRITEW (devc->osdev, devc->azbar + HDA_CORBRP, 0x8000);	/* Reset to 0 */
  PCI_WRITEB (devc->osdev, devc->azbar + HDA_CORBCTL, 0x02);	/* Start */


Initialize RIRB registers

  PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBCTL, 0);	/* Stop */

  tmout = 0;

  while (tmout++ < 500
	 && (PCI_READB (devc->osdev, devc->azbar + HDA_RIRBCTL) & 0x02));

  if (PCI_READB (devc->osdev, devc->azbar + HDA_RIRBCTL) & 0x02)
    {
      cmn_err (CE_WARN, "RIRB didn't stop\n");
    }

  tmp = devc->rirb_phys & 0xffffffff;
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_RIRBLBASE, tmp);
  tmp = (devc->rirb_phys >> 32) & 0xffffffff;
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_RIRBUBASE, tmp);
  devc->rirb_rp = 0;

  PCI_WRITEW (devc->osdev, devc->azbar + HDA_RIRBWP, 0x8000);	/* Reset to 0 */
  PCI_WRITEW (devc->osdev, devc->azbar + HDA_RINTCNT, 1);	/* reset hw write ptr */
  PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBCTL, 0x03);	/* Start & Intr enable */

  return 1;
}

static int
setup_engines (hda_devc_t * devc)
{
  int i, p;
  unsigned int gcap;
  unsigned int num_inputs, num_outputs;
  oss_native_word phaddr;
  gcap = PCI_READW (devc->osdev, devc->azbar + HDA_GCAP);

  num_outputs = (gcap >> 12) & 0x0f;
  num_inputs = (gcap >> 8) & 0x0f;

  /* Init output engines */
  p = num_inputs;		/* Output engines are located after the input ones */

  if (num_outputs > HDA_MAX_ENGINES)
    {
      cmn_err (CE_WARN,
	       "Using only %d out of %d available output engines\n",
	       HDA_MAX_ENGINES, num_outputs);
      num_outputs = HDA_MAX_ENGINES;
    }

  if (num_inputs > HDA_MAX_ENGINES)
    {
      cmn_err (CE_WARN,
	       "Using only %d out of %d available input engines\n",
	       HDA_MAX_ENGINES, num_inputs);
      num_inputs = HDA_MAX_ENGINES;
    }

  for (i = 0; i < num_outputs; i++)
    {
      hda_engine_t *engine = &devc->outengines[i];

      engine->bdl =
	CONTIG_MALLOC (devc->osdev, 4096, MEMLIMIT_32BITS, &phaddr, engine->bdl_dma_handle);
      if (engine->bdl == NULL)
	{
	  cmn_err (CE_WARN, "Out of memory\n");
	  return 0;
	}
      engine->bdl_max = 4096 / sizeof (bdl_t);
      engine->bdl_phys = phaddr;
      engine->base = devc->azbar + 0x80 + (p * 0x20);
      engine->num = i;
      engine->intrmask = (1 << p);
      engine->portc = NULL;
      p++;
      devc->num_outengines = i + 1;
    }

  /* Init input engines */
  p = 0;
  for (i = 0; i < num_inputs; i++)
    {
      hda_engine_t *engine = &devc->inengines[i];

      engine->bdl =
	CONTIG_MALLOC (devc->osdev, 4096, MEMLIMIT_32BITS, &phaddr, engine->bdl_dma_handle);
      if (engine->bdl == NULL)
	{
	  cmn_err (CE_WARN, "Out of memory\n");
	  return 0;
	}
      engine->bdl_max = 4096 / sizeof (bdl_t);
      engine->bdl_phys = phaddr;
      engine->base = devc->azbar + 0x80 + (p * 0x20);
      engine->num = i;
      engine->intrmask = (1 << p);
      engine->portc = NULL;
      p++;
      devc->num_inengines = i + 1;
    }
  return 1;
}

/*ARGSUSED*/
static void
init_adev_caps (hda_devc_t * devc, adev_p adev, hdaudio_endpointinfo_t * ep)
{
  int i;

  adev->oformat_mask = ep->fmt_mask;
  adev->iformat_mask = ep->fmt_mask;

  adev->min_rate = 500000;
  adev->max_rate = 0;

  adev->nrates = ep->nrates;
  if (adev->nrates > OSS_MAX_SAMPLE_RATES)
  {
     cmn_err(CE_NOTE, "Too many supported sample rates (%d)\n", adev->nrates);
     adev->nrates = OSS_MAX_SAMPLE_RATES;
  }

  for (i = 0; i < adev->nrates; i++)
    {
      int r = ep->rates[i];

      adev->rates[i] = r;

      if (adev->min_rate > r)
	adev->min_rate = r;
      if (adev->max_rate < r)
	adev->max_rate = r;
    }
}


S/PDIF lowlevel driver

/*ARGSUSED*/
static int
hdaudio_reprogram_spdif (void *_devc, void *_portc,
			 oss_digital_control * ctl, unsigned int mask)
{
  unsigned short cbits = 0;
  hda_devc_t *devc = _devc;
  oss_native_word flags;
  hdaudio_endpointinfo_t *endpoint = devc->spdifout_endpoint;

  if (endpoint == NULL)
    return OSS_EIO;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

  cbits = 0x01;			/* Digital interface enabled */

  cbits |= (1 << 2);		/* VCFG */

  if (ctl->out_vbit == VBIT_ON)
    cbits |= (1 << 1);		/* Turn on the V bit */

  if (ctl->cbitout[0] & 0x02)	/* Audio/Data */
    cbits |= (1 << 5);		/* /AUDIO */

  if (ctl->cbitout[0] & 0x01)	/* Pro */
    cbits |= (1 << 6);
  else
    {

      if (ctl->cbitout[0] & 0x04)	/* Copyright */
	cbits |= (1 << 4);	/* COPY */

      if (ctl->cbitout[1] & 0x80)	/* Generation level */
	cbits |= (1 << 7);	/* L */

      if (ctl->emphasis_type & 1)	/* Pre-emphasis */
	cbits |= (1 << 3);	/* PRE */
    }
  do_corb_write (devc, endpoint->cad, endpoint->base_wid, 0,
		 SET_SPDIF_CONTROL1, cbits);

  cbits = ctl->cbitout[1] & 0x7f;	/* Category code */
  do_corb_write (devc, endpoint->cad, endpoint->base_wid, 0,
		 SET_SPDIF_CONTROL2, cbits);

  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 0;
}

spdif_driver_t hdaudio_spdif_driver = {
/* reprogram_device: */ hdaudio_reprogram_spdif,
};

static int
spdif_mixer_init (int dev)
{
  hda_devc_t *devc = mixer_devs[dev]->hw_devc;
  int err;

  if ((err = oss_spdif_mix_init (&devc->spdc)) < 0)
    return err;

  return 0;
}

static void
install_outputdevs (hda_devc_t * devc)
{
  int i, n, pass, audio_dev, output_num=0;
  char tmp_name[64];
  hdaudio_endpointinfo_t *endpoints;

  if ((n =
       hdaudio_mixer_get_outendpoints (devc->mixer, &endpoints,
				       sizeof (*endpoints))) < 0)
    return;

#if 0
  // This check will be done later.
  if (n > MAX_OUTPUTS)
    {
      cmn_err (CE_WARN,
	       "Only %d out of %d output devices can be installed\n",
	       MAX_OUTPUTS, n);
      n = MAX_OUTPUTS;
    }
#endif


Install the output devices in two passes. First install the analog endpoints and then the digital one(s).

  for (pass = 0; pass < 3; pass++)
    for (i = 0; i < n; i++)
      {
	adev_p adev;
	hda_portc_t *portc = &devc->output_portc[i];
	unsigned int formats = AFMT_S16_LE;
	int opts = ADEV_AUTOMODE | ADEV_NOINPUT;
	char *devfile_name = "";
	char devname[16];

/* Skip endpoints that are not physically connected on the motherboard. */
	if (endpoints[i].skip)
	  continue;

	if (output_num >= MAX_OUTPUTS)
	{
		cmn_err(CE_CONT, "Too many output endpoints. Endpoint %d ignored.\n", i);
		continue;
	}

	switch (pass)
	  {
	  case 0:		/* Pick analog and non-modem ones */
	    if (endpoints[i].is_digital || endpoints[i].is_modem)
	      continue;
	    break;

	  case 1:		/* Pick digital one(s) */
	    if (!endpoints[i].is_digital)
	      continue;
            break;

          case 2:               /* Pick modem one(s) */
            if (!endpoints[i].is_modem)
              continue;
	  }

	if (endpoints[i].is_digital)
	  {
	    opts |= ADEV_SPECIAL;
	    sprintf (devname, "spdout%d", devc->num_spdout++);
	    devfile_name = devname;
	  }
        if (endpoints[i].is_modem)
          {
            opts |= ADEV_SPECIAL;
            sprintf (devname, "mdmout%d", devc->num_mdmout++);
            devfile_name = devname;
          }

	// sprintf (tmp_name, "%s %s", devc->chip_name, endpoints[i].name);
	sprintf (tmp_name, "HD Audio play %s", endpoints[i].name);

	if ((audio_dev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION,
					       devc->osdev,
					       devc->osdev,
					       tmp_name,
					       &hda_audio_driver,
					       sizeof (audiodrv_t),
					       opts, formats, devc, -1,
					       devfile_name)) < 0)
	  {
	    return;
	  }

	if (output_num == 0)
	  devc->first_dev = audio_dev;

	adev = audio_engines[audio_dev];
	devc->num_outputs = i+1;

	adev->devc = devc;
	adev->portc = portc;
	adev->rate_source = devc->first_dev;
	adev->mixer_dev = devc->mixer_dev;
	adev->min_rate = 8000;
	adev->max_rate = 192000;
	adev->min_channels = 2;

	if (output_num == 0)
	  adev->max_channels = 8;
	else
	  adev->max_channels = 2;

        if (endpoints[i].is_modem)
          {
            adev->min_channels = 1;
            adev->max_channels = 1;
            adev->caps |= PCM_CAP_MODEM;
          }

	if (adev->max_channels > 4)
	  adev->dmabuf_alloc_flags |= DMABUF_LARGE | DMABUF_QUIET;
	adev->min_block = 128;	/* Hardware limitation */
	adev->min_fragments = 4;	/* Vmix doesn't work without this */
	portc->num = output_num;
	portc->open_mode = 0;
	portc->audio_enabled = 0;
	portc->audiodev = audio_dev;
	portc->port_type = PT_OUTPUT;
	portc->endpoint = &endpoints[i];
	init_adev_caps (devc, adev, portc->endpoint);
	portc->engine = NULL;

	if (portc->endpoint->is_digital)
	  {
	    int err;

	    devc->spdifout_endpoint = portc->endpoint;


To be precise the right place for the spdc field might be portc instead of devc. In this way it's possible to have multiple S/PDIF output DACs connected to the same HDA controller. OTOH having it in devc saves space. Multiple oss_spdif_install() calls on the same spdc structure doesn't cause any problems.

If spdc is moved to portc then care must be taken that oss_spdif_uninstall() is called for all all output_portc instances.

	    if ((err = oss_spdif_install (&devc->spdc, devc->osdev,
					  &hdaudio_spdif_driver,
					  sizeof (spdif_driver_t), devc, NULL,
					  devc->mixer_dev, SPDF_OUT,
					  DIG_PASSTHROUGH | DIG_EXACT |
					  DIG_CBITOUT_LIMITED | DIG_VBITOUT |
					  DIG_PRO | DIG_CONSUMER)) != 0)
	      {
		cmn_err (CE_WARN,
			 "S/PDIF driver install failed. Error %d\n", err);
	      }
	    else
	      {
		hdaudio_mixer_set_initfunc (devc->mixer, spdif_mixer_init);
	      }
	  }
	  output_num++;
      }
}

static void
install_inputdevs (hda_devc_t * devc)
{
  int i, n, audio_dev;
  char tmp_name[64];
  hdaudio_endpointinfo_t *endpoints;
  char devname[16], *devfile_name = "";

  if ((n =
       hdaudio_mixer_get_inendpoints (devc->mixer, &endpoints,
				      sizeof (*endpoints))) < 0)
    return;

  if (n > MAX_INPUTS)
    {
      cmn_err (CE_WARN,
	       "Only %d out of %d input devices can be installed\n",
	       MAX_INPUTS, n);
      n = MAX_INPUTS;
    }

  for (i = 0; i < n; i++)
    {
      adev_p adev;
      hda_portc_t *portc = &devc->input_portc[i];
      unsigned int formats = AFMT_S16_LE;
      int opts = ADEV_AUTOMODE | ADEV_NOOUTPUT;

/* Skip endpoints that are not physically connected on the motherboard. */
      if (endpoints[i].skip)
	continue;

      if (endpoints[i].is_digital)
	{
	  opts |= ADEV_SPECIAL;
	  sprintf (devname, "spdin%d", devc->num_spdin++);
	  devfile_name = devname;
	}
      if (endpoints[i].is_modem)
        {
          opts |= ADEV_SPECIAL;
          sprintf (devname, "mdmin%d", devc->num_mdmin++);
          devfile_name = devname;
        }

      //sprintf (tmp_name, "%s %s", devc->chip_name, endpoints[i].name);
      sprintf (tmp_name, "HD Audio rec %s", endpoints[i].name);

      if ((audio_dev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION,
					     devc->osdev,
					     devc->osdev,
					     tmp_name,
					     &hda_audio_driver,
					     sizeof (audiodrv_t),
					     opts, formats, devc, -1,
					     devfile_name)) < 0)
	{
	  return;
	}

      if (i == 0 && devc->first_dev == -1)
	devc->first_dev = audio_dev;

      adev = audio_engines[audio_dev];
      devc->num_inputs = i+1;

      adev->devc = devc;
      adev->portc = portc;
      adev->rate_source = devc->first_dev;
      adev->mixer_dev = devc->mixer_dev;
      adev->min_rate = 8000;
      adev->max_rate = 192000;
      adev->min_channels = 2;
      adev->max_channels = 2;

      if (endpoints[i].is_modem)
        {
          adev->min_channels = 1;
          adev->max_channels = 1;
          adev->caps |= PCM_CAP_MODEM;
        }

      adev->min_block = 128;	/* Hardware limitation */
      adev->min_fragments = 4;	/* Vmix doesn't work without this */
      portc->num = i;
      portc->open_mode = 0;
      portc->audio_enabled = 0;
      portc->audiodev = audio_dev;
      portc->port_type = PT_INPUT;
      portc->endpoint = &endpoints[i];
      portc->engine = NULL;
      init_adev_caps (devc, adev, portc->endpoint);
    }
}

static void
activate_vmix (hda_devc_t * devc)
{
#ifdef CONFIG_OSS_VMIX

Attach vmix engines to all outputs and inputs.


	if (devc->num_outputs > 0)
	   {
		   if (devc->num_inputs < 1)
		      vmix_attach_audiodev(devc->osdev, devc->output_portc[0].audiodev, -1, 0);
		   else
  		      vmix_attach_audiodev(devc->osdev, devc->output_portc[0].audiodev, devc->input_portc[0].audiodev, 0);
	   };
#endif
}

static int
init_HDA (hda_devc_t * devc)
{
  unsigned int gcap;
  unsigned int tmp;

  /* Reset controller */

  if (!reset_controller (devc))
    return 0;

  PCI_WRITEL (devc->osdev, devc->azbar + HDA_INTCTL, PCI_READL (devc->osdev, devc->azbar + HDA_INTCTL) | 0xc0000000);	/* Intr enable */


Set CORB and RIRB sizes to 256, 16 or 2 entries.

  tmp = (PCI_READB (devc->osdev, devc->azbar + HDA_CORBSIZE) >> 4) & 0x07;
  if (tmp & 0x4)		/* 256 entries */
    PCI_WRITEB (devc->osdev, devc->azbar + HDA_CORBSIZE, 0x2);
  else if (tmp & 0x2)		/* 16 entries */
    PCI_WRITEB (devc->osdev, devc->azbar + HDA_CORBSIZE, 0x1);
  else				/* Assume that 2 entries is supported */
    PCI_WRITEB (devc->osdev, devc->azbar + HDA_CORBSIZE, 0x0);

  tmp = (PCI_READB (devc->osdev, devc->azbar + HDA_RIRBSIZE) >> 4) & 0x07;
  if (tmp & 0x4)		/* 256 entries */
    PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBSIZE, 0x2);
  else if (tmp & 0x2)		/* 16 entries */
    PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBSIZE, 0x1);
  else				/* Assume that 2 entries is supported */
    PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBSIZE, 0x0);

  /* setup the CORB/RIRB structs */
  if (!setup_controller (devc))
    return 0;

  /* setup the engine structs */
  if (!setup_engines (devc))
    return 0;

  if (!devc->codecmask)
    {
      cmn_err (CE_WARN, "No codecs found after reset\n");
      return 0;
    }
  else
    devc->mixer = hdaudio_mixer_create (devc->chip_name, devc, devc->osdev,
					do_corb_write, corb_read,
					devc->codecmask, devc->vendor_id,
					devc->subvendor_id);
  if (devc->mixer == NULL)
    return 0;

  devc->mixer_dev = hdaudio_mixer_get_mixdev (devc->mixer);

  gcap = PCI_READW (devc->osdev, devc->azbar + HDA_GCAP);

  if (((gcap >> 3) & 0x0f) > 0)
    cmn_err (CE_WARN, "Bidirectional engines not supported\n");
  if (((gcap >> 1) & 0x03) > 0)
    cmn_err (CE_WARN, "Multiple SDOs not supported\n");

  install_outputdevs (devc);
  install_inputdevs (devc);
  activate_vmix (devc);

  return 1;
}


int
oss_hdaudio_attach (oss_device_t * osdev)
{
  unsigned char pci_irq_line, pci_revision, btmp;
  unsigned short pci_command, vendor, device, wtmp;
  unsigned short subvendor, subdevice;
  hda_devc_t *devc;
  static int already_attached = 0;
  int err;

  DDB (cmn_err (CE_CONT, "oss_hdaudio_attach entered\n"));

  if (already_attached)
    {
      cmn_err (CE_WARN, "oss_hdaudio_attach: Already attached\n");
      return 0;
    }
  already_attached = 1;

  pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor);
  pci_read_config_word (osdev, PCI_DEVICE_ID, &device);

#if 0
  // This check is not necessary because the kernel has already checked
  // the vendor&device ID

  if ((vendor != INTEL_VENDOR_ID && vendor != NVIDIA_VENDOR_ID &&
       vendor != ATI_VENDOR_ID && vendor != SIS_VENDOR_ID &&
       vendor != VIA_VENDOR_ID && vendor != ULI_VENDOR_ID) ||
      (device != INTEL_DEVICE_ICH6 && device != INTEL_DEVICE_ICH7 &&
       device != INTEL_DEVICE_ESB2 && device != INTEL_DEVICE_ICH8 &&
       device != INTEL_DEVICE_ICH9 && device != INTEL_DEVICE_P35 &&
       device != INTEL_DEVICE_ICH10 && device != INTEL_DEVICE_ICH10_B &&
       device != INTEL_DEVICE_PCH &&
       device != NVIDIA_DEVICE_MCP51 && device != NVIDIA_DEVICE_MCP55 &&
       device != NVIDIA_DEVICE_MCP61 && device != NVIDIA_DEVICE_MCP61A &&
       device != NVIDIA_DEVICE_MCP65 && device != NVIDIA_DEVICE_MCP67 &&
       device != NVIDIA_DEVICE_MCP73 && device != NVIDIA_DEVICE_MCP78S &&
       device != NVIDIA_DEVICE_MCP79 &&
       device != VIA_DEVICE_HDA &&
       device != SIS_DEVICE_HDA &&
       device != ULI_DEVICE_HDA &&
       device != ATI_DEVICE_SB450 && device != ATI_DEVICE_SB600))

    {
      return 0;
    }
#endif

  pci_read_config_byte (osdev, PCI_REVISION_ID, &pci_revision);
  pci_read_config_word (osdev, PCI_COMMAND, &pci_command);
  pci_read_config_irq (osdev, PCI_INTERRUPT_LINE, &pci_irq_line);
  pci_read_config_word (osdev, PCI_SUBSYSTEM_VENDOR_ID, &subvendor);
  pci_read_config_word (osdev, PCI_SUBSYSTEM_ID, &subdevice);

  if ((devc = PMALLOC (osdev, sizeof (*devc))) == NULL)
    {
      cmn_err (CE_WARN, "Out of memory\n");
      return 0;
    }

  devc->osdev = osdev;
  osdev->devc = devc;
  devc->first_dev = -1;

  osdev->hw_info = PMALLOC (osdev, HWINFO_SIZE);	/* Text buffer for additional device info */
  memset (osdev->hw_info, 0, HWINFO_SIZE);

  devc->vendor_id = (vendor << 16) | device;
  devc->subvendor_id = (subvendor << 16) | subdevice;

  oss_pci_byteswap (osdev, 1);

  switch (device)
    {
    case INTEL_DEVICE_ICH6:
    case INTEL_DEVICE_ICH7:
    case INTEL_DEVICE_ESB2:
    case INTEL_DEVICE_ICH8:
    case INTEL_DEVICE_ICH9:
    case INTEL_DEVICE_P35:
    case INTEL_DEVICE_ICH10:
    case INTEL_DEVICE_ICH10_B:
    case INTEL_DEVICE_PCH:
      devc->chip_name = "Intel HD Audio";
      break;

    case NVIDIA_DEVICE_MCP51:
    case NVIDIA_DEVICE_MCP55:
    case NVIDIA_DEVICE_MCP61:
    case NVIDIA_DEVICE_MCP61A:
    case NVIDIA_DEVICE_MCP65:
    case NVIDIA_DEVICE_MCP67:
    case NVIDIA_DEVICE_MCP73:
    case NVIDIA_DEVICE_MCP78S:
    case NVIDIA_DEVICE_MCP79:
      devc->chip_name = "nVidia HD Audio";
      pci_read_config_byte (osdev, 0x4e, &btmp);
      pci_write_config_byte (osdev, 0x4e, (btmp & 0xf0) | 0x0f);
      pci_read_config_byte (osdev, 0x4d, &btmp);
      pci_write_config_byte (osdev, 0x4d, (btmp & 0xfe) | 0x01);
      pci_read_config_byte (osdev, 0x4c, &btmp);
      pci_write_config_byte (osdev, 0x4c, (btmp & 0xfe) | 0x01);
      break;

    case ATI_DEVICE_SB450:
    case ATI_DEVICE_SB600:
      devc->chip_name = "ATI HD Audio";
      pci_read_config_byte (osdev, 0x42, &btmp);
      pci_write_config_byte (osdev, 0x42, (btmp & 0xf8) | 0x2);
      break;

    case VIA_DEVICE_HDA:
      devc->chip_name = "VIA HD Audio";
      break;

    case SIS_DEVICE_HDA:
      devc->chip_name = "SiS HD Audio";
      break;

    case ULI_DEVICE_HDA:
      devc->chip_name = "ULI HD Audio";
      pci_read_config_word (osdev, 0x40, &wtmp);
      pci_write_config_word (osdev, 0x40, wtmp | 0x10);
      pci_write_config_dword (osdev, PCI_MEM_BASE_ADDRESS_1, 0);
      break;

    case CREATIVE_XFI_HDA:
      devc->chip_name = "Creative Labs XFi XTreme Audio";
      break;
    }

  pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_0, &devc->membar_addr);

  devc->membar_addr &= ~7;

  /* get virtual address */
  devc->azbar =
    (void *) MAP_PCI_MEM (devc->osdev, 0, devc->membar_addr, 16 * 1024);

  /* activate the device */
  pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY;
  pci_write_config_word (osdev, PCI_COMMAND, pci_command);

  if (pci_irq_line == 0)
    {
      cmn_err (CE_WARN, "IRQ not assigned by BIOS.\n");
      return 0;
    }

  devc->irq = pci_irq_line;

  MUTEX_INIT (devc->osdev, devc->mutex, MH_DRV);
  MUTEX_INIT (devc->osdev, devc->low_mutex, MH_DRV + 1);

  oss_register_device (osdev, devc->chip_name);

  if ((err = oss_register_interrupts (devc->osdev, 0, hdaintr, NULL)) < 0)
    {
      cmn_err (CE_WARN, "Can't register interrupt handler, err=%d\n", err);
      return 0;
    }

  devc->base = devc->membar_addr;

  /* Setup the TCSEL register. Don't do this with ATI chipsets. */
  if (vendor != ATI_VENDOR_ID)
    {
      pci_read_config_byte (osdev, 0x44, &btmp);
      pci_write_config_byte (osdev, 0x44, btmp & 0xf8);
    }

  err = init_HDA (devc);
  return err;
}

int
oss_hdaudio_detach (oss_device_t * osdev)
{
  hda_devc_t *devc = (hda_devc_t *) osdev->devc;
  int j;

  if (oss_disable_device (osdev) < 0)
    return 0;

  PCI_WRITEL (devc->osdev, devc->azbar + HDA_INTSTS, 0xc0000000);	/* ack pending ints */

  PCI_WRITEL (devc->osdev, devc->azbar + HDA_INTCTL, 0);	/* Intr disable */
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_STATESTS, 0x7);	/* Intr disable */
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_RIRBSTS, 0x5);	/* Intr disable */
  PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBCTL, 0);	/* Stop */
  PCI_WRITEB (devc->osdev, devc->azbar + HDA_CORBCTL, 0);	/* Stop */

  oss_unregister_interrupts (devc->osdev);

  MUTEX_CLEANUP (devc->mutex);
  MUTEX_CLEANUP (devc->low_mutex);

  if (devc->corb != NULL)
    CONTIG_FREE (devc->osdev, devc->corb, 4096, devc->corb_dma_handle);

  devc->corb = NULL;

  for (j = 0; j < devc->num_outengines; j++)
    {
      hda_engine_t *engine = &devc->outengines[j];

      if (engine->bdl == NULL)
	continue;
      CONTIG_FREE (devc->osdev, engine->bdl, 4096, engine->bdl_dma_handle);
    }

  for (j = 0; j < devc->num_inengines; j++)
    {
      hda_engine_t *engine = &devc->inengines[j];

      if (engine->bdl == NULL)
	continue;
      CONTIG_FREE (devc->osdev, engine->bdl, 4096, engine->bdl_dma_handle);
    }

  if (devc->membar_addr != 0)
    {
      UNMAP_PCI_MEM (devc->osdev, 0, devc->membar_addr, devc->azbar,
		     16 * 1024);
      devc->membar_addr = 0;
    }

  oss_spdif_uninstall (&devc->spdc);

  oss_unregister_device (devc->osdev);
  return 1;
}

Copyright (C) 4Front Technologies, 2007. All rights reserved.

Back to index OSS web site


Copyright (C) 4Front Technologies, 2007. All rights reserved.
Back to index OSS web site