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_atiaudio/oss_atiaudio.c

Driver for the ATI IXP (AC97) audio controller

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_atiaudio_cfg.h"
#include "oss_pci.h"
#include "ac97.h"

#define ATI_VENDOR_ID           0x1002
#define ATI_DEVICE_IXP         	0x4341
#define ATI_DEVICE_IXP300       0x4361
#define ATI_DEVICE_IXP400       0x4370

#define MAX_PORTC 3
#define BDL_SIZE	64

typedef struct
{
  int open_mode;
  int speed, bits, channels;
  int audio_enabled;
  int trigger_bits;
  int audiodev;
  int port_type;
#define DF_ANALOG 0
#define DF_SPDIF 1
}
ATI_portc;

typedef struct
{
  unsigned int addr;
  unsigned short status;
  unsigned short size;
  unsigned int next;
}
bdl_t;

typedef struct
{
  oss_device_t *osdev;
  oss_native_word membar_addr;
  char *membar_virt;
  int irq;
  oss_mutex_t mutex;
  oss_mutex_t low_mutex;

  /* Mixer */
  ac97_devc ac97devc;
  int mixer_dev;
  int inverted;

  /* Audio parameters */
  int open_mode;

  /* Buffer Descriptor List */
  char *bdlBuffer;
  bdl_t *playBDL, *recBDL, *spdifBDL;
  oss_native_word playBDL_phys, recBDL_phys, spdifBDL_phys;
  oss_dma_handle_t bldbuf_dma_handle;

  int play_currbuf, play_currfrag;
  int rec_currbuf, rec_currfrag;
  char *chip_name;
  ATI_portc portc[MAX_PORTC];
  int play_frag_index[BDL_SIZE];
  int rec_frag_index[BDL_SIZE];
}
ATI_devc;


/* Mem mapped I/O registers */
#define READL(o,a)       *(volatile unsigned int*)(devc->membar_virt+(a))
#define READW(o,a)       *(volatile unsigned short*)(devc->membar_virt+(a))
#define READB(o,a)       *(volatile unsigned char*)(devc->membar_virt+(a))
#define WRITEL(o,d,a)    *(volatile unsigned int*)(devc->membar_virt+(a))=d
#define WRITEW(o,d,a)    *(volatile unsigned short*)(devc->membar_virt+(a))=d
#define WRITEB(o,d,a)    *(volatile unsigned char*)(devc->membar_virt+(a))=d


static int
ac97_ready (void *devc_)
{
  ATI_devc *devc = devc_;
  int timeout = 10000;

  while (READL (devc->osdev, 0x0c) & 0x100)
    {
      if (!timeout--)
	{
	  cmn_err (CE_WARN, "codec ready timed out\n");
	  return OSS_EIO;
	}
      oss_udelay (10);
    }
  return 0;
}

static int
ac97_read (void *devc_, int reg)
{
  ATI_devc *devc = devc_;
  unsigned int data = 0;
  unsigned int addr = 0;
  int timeout;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

again:
  if (ac97_ready (devc) < 0)
    {
      cmn_err (CE_WARN, "ac97 not ready\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return OSS_EIO;
    }

  addr = (unsigned int) (reg << 9) | (1 << 8) | (1 << 2);
  WRITEL (devc->osdev, addr, 0x0c);	/* Set read commmand */

  if (ac97_ready (devc) < 0)
    {
      cmn_err (CE_WARN, "ac97 not ready\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return OSS_EIO;
    }

  timeout = 1000;

  for (;;)
    {
      data = READL (devc->osdev, 0x10);	/* ac97 data port */
      if (data & 0x100)
	{
	  /* if the register returned isn't the reg sent then resend command */
	  if ((data & 0xFE00) != (reg << 9))
	    goto again;
	  else
	    break;
	}

      if (!--timeout)
	{
	  cmn_err (CE_WARN, "AC97 read timed out\n");
	  break;
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return (data >> 16);
}


static int
ac97_write (void *devc_, int reg, int data)
{
  ATI_devc *devc = devc_;
  oss_native_word flags;
  unsigned int val;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  if (ac97_ready (devc) < 0)
    {
      cmn_err (CE_WARN, "AC97 not ready\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return 0;
    }

  val = (data << 16) | (reg << 9) | 0x100;
  WRITEL (devc->osdev, val, 0x0c);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 1;
}

static int
ATIIXPintr (oss_device_t * osdev)
{
  int status, i;
  int serviced = 0;
  ATI_devc *devc = (ATI_devc *) osdev->devc;
  ATI_portc *portc;
  /* oss_native_word flags; */


TODO: Enable the mutexes and move inputintr/outputintr calls outside the mutex region.


  /* MUTEX_ENTER (devc->mutex, flags); */
  /* Handle Global Interrupts */
  status = READL (devc->osdev, 0x00);

  /* handle PCM output */
  if (status & 0x08)
    for (i = 0; i < MAX_PORTC; i++)
      {
	portc = &devc->portc[i];
	if ((portc->trigger_bits & PCM_ENABLE_OUTPUT)
	    && (portc->port_type == DF_ANALOG))
	  {
	    dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out;
	    int ptr=0, n = 0;
	    int timeout = 1000;
	    serviced = 1;
	    while (timeout--)
	      {
		ptr = READL (devc->osdev, 0x44);

		if (ptr < dmap->dmabuf_phys)
		  continue;

		ptr -= dmap->dmabuf_phys;

		if (ptr >= dmap->bytes_in_use)
		  continue;

		break;
	      }

	    if (dmap->fragment_size == 0)
	      {
		cmn_err (CE_WARN, "dmap->fragment_size == 0\n");
		continue;
	      }

	    ptr /= dmap->fragment_size;

	    if ((ptr < 0) || (ptr > dmap->nfrags))
	      ptr = 0;

	    n = 0;
	    while ((dmap_get_qhead (dmap) != ptr) && (n++ < dmap->nfrags))
	      oss_audio_outputintr (portc->audiodev, 1);
	  }
      }

  /* handle PCM input */
  if (status & 0x02)
    for (i = 0; i < MAX_PORTC; i++)
      {
	portc = &devc->portc[i];
	if ((portc->trigger_bits & PCM_ENABLE_INPUT)
	    && (portc->port_type == DF_ANALOG))
	  {
	    dmap_t *dmap = audio_engines[portc->audiodev]->dmap_in;
	    int ptr=0, n = 0;
	    int timeout = 1000;
	    serviced = 1;

	    while (timeout--)
	      {
		ptr = READL (devc->osdev, 0x2c);

		if (ptr < dmap->dmabuf_phys)
		  continue;

		ptr -= dmap->dmabuf_phys;

		if (ptr >= dmap->bytes_in_use)
		  continue;

		break;
	      }

	    if (dmap->fragment_size == 0)
	      {
		cmn_err (CE_WARN, "dmap->fragment_size == 0\n");
		continue;
	      }

	    ptr /= dmap->fragment_size;

	    if ((ptr < 0) || (ptr > dmap->nfrags))
	      ptr = 0;

	    n = 0;
	    while ((dmap_get_qtail (dmap) != ptr) && (n++ < dmap->nfrags))
	      oss_audio_inputintr (portc->audiodev, 0);
	  }
      }

  /* handle SPDIF output */
  if (status & 0x20)
    {
      portc = &devc->portc[2];
      if ((portc->trigger_bits & PCM_ENABLE_OUTPUT)
	  && (portc->port_type == DF_SPDIF))
	{
	  dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out;
	  int ptr=0, n = 0;
	  int timeout = 1000;
	  serviced = 1;

	  while (timeout--)
	    {
	      ptr = READL (devc->osdev, 0x5c);
	      if (ptr < dmap->dmabuf_phys)
		continue;
	      ptr -= dmap->dmabuf_phys;
	      if (ptr >= dmap->bytes_in_use)
		continue;
	      break;
	    }

	  if (dmap->fragment_size == 0)
	    cmn_err (CE_WARN, "dmap->fragment_size == 0\n");
	  else
	    {
	      ptr /= dmap->fragment_size;

	      if ((ptr < 0) || (ptr > dmap->nfrags))
		ptr = 0;

	      n = 0;
	      while ((dmap_get_qhead (dmap) != ptr) && (n++ < dmap->nfrags))
		oss_audio_outputintr (portc->audiodev, 1);
	    }
	}
    }

  /* Acknowledge Interrupts */
  WRITEL (devc->osdev, status, 0x00);
  /* MUTEX_EXIT (devc->mutex, flags); */
  return serviced;
}


Audio routines


static int
ATI_audio_set_rate (int dev, int arg)
{
  ATI_portc *portc = audio_engines[dev]->portc;

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

  if (audio_engines[dev]->flags & ADEV_FIXEDRATE)
    arg = 48000;

  if (arg > 48000)
    arg = 48000;
  if (arg < 5000)
    arg = 5000;
  portc->speed = arg;
  return portc->speed;
}

static short
ATI_audio_set_channels (int dev, short arg)
{
  ATI_portc *portc = audio_engines[dev]->portc;

  if ((arg == 1) || (arg == 2))
    {
      audio_engines[dev]->flags |= ADEV_STEREOONLY;
      arg = 2;
    }
  else
    audio_engines[dev]->flags &= ~ADEV_STEREOONLY;

  if (arg>6)
     arg=6;

  if ((arg != 1) && (arg != 2) && (arg != 4) && (arg != 6))
    return portc->channels;
  portc->channels = arg;

  return portc->channels;
}

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

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

  if (!(arg & (AFMT_U8 | AFMT_S16_LE | AFMT_AC3)))
    return portc->bits;
  portc->bits = arg;

  return portc->bits;
}

/*ARGSUSED*/
static int
ATI_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  return OSS_EINVAL;
}

static void ATI_audio_trigger (int dev, int state);

static void
ATI_audio_reset (int dev)
{
  ATI_audio_trigger (dev, 0);
}

static void
ATI_audio_reset_input (int dev)
{
  ATI_portc *portc = audio_engines[dev]->portc;
  ATI_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
ATI_audio_reset_output (int dev)
{
  ATI_portc *portc = audio_engines[dev]->portc;
  ATI_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

/*ARGSUSED*/
static int
ATI_audio_open (int dev, int mode, int openflags)
{
  ATI_portc *portc = audio_engines[dev]->portc;
  ATI_devc *devc = audio_engines[dev]->devc;
  oss_native_word flags;

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

  portc->open_mode = mode;
  portc->audio_enabled &= ~mode;
  devc->open_mode |= mode;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

static void
ATI_audio_close (int dev, int mode)
{
  ATI_portc *portc = audio_engines[dev]->portc;
  ATI_devc *devc = audio_engines[dev]->devc;

  ATI_audio_reset (dev);
  portc->open_mode = 0;
  devc->open_mode &= ~mode;
  portc->audio_enabled &= ~mode;
}

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

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

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

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

static void
ATI_enable_dma (int dev, int direction, int enable)
{
  ATI_devc *devc = audio_engines[dev]->devc;
  ATI_portc *portc = audio_engines[dev]->portc;

  if (enable)
    {
      if (direction)
	{
	  /* flush fifo and enable analog or spdif DMA */
	  WRITEL (devc->osdev, READL (devc->osdev, 0x88) | 1, 0x88);

	  if (portc->port_type == DF_ANALOG)
	    WRITEL (devc->osp, READL (devc->osp, 0x08) | 1 << 9, 0x08);
	  else
	    WRITEL (devc->osp, READL (devc->osp, 0x08) | 1 << 10, 0x08);

	}
      else
	{
	  /* flush fifo */
	  WRITEL (devc->osdev, READL (devc->osdev, 0x88) | 2, 0x88);
	  WRITEL (devc->osdev, READL (devc->osdev, 0x08) | 1 << 8, 0x08);
	}
    }
  else
    {
      if (direction)
	{
	  if (portc->port_type == DF_ANALOG)
	    WRITEL (devc->osp, READL (devc->osp, 0x08) & ~(1 << 9), 0x08);
	  else
	    WRITEL (devc->osp, READL (devc->osp, 0x08) & ~(1 << 10), 0x08);
	}
      else
	WRITEL (devc->osdev, READL (devc->osdev, 0x08) & ~(1 << 8), 0x08);
    }
}

static void
ATI_audio_trigger (int dev, int state)
{
  ATI_devc *devc = audio_engines[dev]->devc;
  ATI_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (portc->open_mode & OPEN_WRITE)
    {
      if (state & PCM_ENABLE_OUTPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      ATI_enable_dma (dev, 1, 1);

	      /* set BDL & enable analog or SPDIF DMA */
	      if (portc->port_type == DF_ANALOG)
		{
		  WRITEL (devc->osp, devc->playBDL_phys | 1, 0x38);
		  WRITEL (devc->osp, READL (devc->osp, 0x08) | 0x04, 0x08);
		}
	      else		/* SPDIF */
		{
		  WRITEL (devc->osp, devc->spdifBDL_phys | 1, 0x50);
		  WRITEL (devc->osp, READL (devc->osp, 0x08) | 0x10, 0x08);
		}

	      /* set bus busy */
	      WRITEL (devc->osdev, READL (devc->osdev, 0x04) | 1 << 14, 0x04);

	      portc->trigger_bits |= PCM_ENABLE_OUTPUT;
	    }
	}
      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;

	      ATI_enable_dma (dev, 1, 0);

	      /* Stop Anlog or SPDIF DMA */
	      if (portc->port_type == DF_ANALOG)
		WRITEL (devc->osp, READL (devc->osp, 0x08) & ~0x04, 0x08);
	      else
		WRITEL (devc->osp, READL (devc->osp, 0x08) & ~0x10, 0x08);

	      /* set bus clear */
	      WRITEL (devc->osdev, READL (devc->osdev, 0x04) & ~(1 << 14),
		      0x04);

	    }
	}
    }

  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;
	      ATI_enable_dma (dev, 0, 1);
	      WRITEL (devc->osdev, devc->recBDL_phys | 1, 0x20);

	      /* enable audio intput and enable input DMA */
	      WRITEL (devc->osdev, READL (devc->osdev, 0x08) | 0x02, 0x08);
	      /* set bus busy */
	      WRITEL (devc->osdev, READL (devc->osdev, 0x04) | (1 << 14),
		      0x04);
	    }
	}
      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;
	      ATI_enable_dma (dev, 0, 0);
	      /* stop audio input */
	      WRITEL (devc->osdev, READL (devc->osdev, 0x08) & ~0x02, 0x08);
	      /* clear bus busy */
	      WRITEL (devc->osdev, READL (devc->osdev, 0x04) & ~(1 << 14),
		      0x04);
	    }
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

/*ARGSUSED*/
static int
ATI_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  ATI_devc *devc = audio_engines[dev]->devc;
  ATI_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  int i, n;
  unsigned int data;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  /* set the bits */
  data = READL (devc->osdev, 0x08) & ~(1 << 21);
  if (portc->bits == 16)
    data |= (1 << 21);
  WRITEL (devc->osdev, data, 0x08);	/*set 8/16 bits */

  n = dmap->nfrags;
  if (n > BDL_SIZE)
    {
      cmn_err (CE_WARN, "Internal error - BDL too small\n");
      return OSS_EIO;
    }

  for (i = 0; i < n; i++)
    {
      int next = i + 1;
      if (i == n - 1)
	next = 0;

      devc->recBDL[i].addr = dmap->dmabuf_phys + (i * dmap->fragment_size);
      devc->recBDL[i].status = 0;
      devc->recBDL[i].size = dmap->fragment_size >> 2;
      devc->recBDL[i].next = devc->recBDL_phys + next * sizeof (bdl_t);
    }

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

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

/*ARGSUSED*/
static int
ATI_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  ATI_devc *devc = audio_engines[dev]->devc;
  ATI_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_out;
  int i, n;
  unsigned int data;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);


  if (portc->port_type == DF_SPDIF)
    {
      ac97_spdif_setup (devc->mixer_dev, portc->speed, portc->bits);

      if (portc->bits == AFMT_AC3)
	{
	  portc->channels = 2;
	  portc->bits = 16;
	}

      n = dmap->nfrags;
      if (n > BDL_SIZE)
	n = BDL_SIZE;

      for (i = 0; i < n; i++)
	{
	  int next = i + 1;
	  if (i == n - 1)
	    next = 0;

	  devc->spdifBDL[i].addr =
	    dmap->dmabuf_phys + (i * dmap->fragment_size);
	  devc->spdifBDL[i].status = 0;
	  devc->spdifBDL[i].size = dmap->fragment_size >> 2;
	  devc->spdifBDL[i].next =
	    devc->spdifBDL_phys + next * sizeof (bdl_t);
	}

    }
  else
    {
      data = READL (devc->osdev, 0x34) & ~0x3ff;
      switch (portc->channels)
	{
	case 6:
	  data |= 0x30;		/* slots 7, 8 */
	  break;
	case 4:
	  data |= 0x48;		/* slts 6, 9 */
	  break;
	default:
	  data |= 0x03;		/* slots 3, 4 */
	  break;
	}
      data |= 4 << 11;
      WRITEL (devc->osdev, data, 0x34);

      /* set the bits */
      data = READL (devc->osdev, 0x08) & ~(1 << 22);
      if (portc->bits == 16)
	data |= (1 << 22);
      WRITEL (devc->osdev, data, 0x08);	/*set 8/16 bits */
#if 0
      /* set 6 channel reorder */
      if (portc->channels >= 6)
	WRITEL (devc->osdev, 0x1, 0x84);
      else
	WRITEL (devc->osdev, 0x00, 0x84);
#endif
      n = dmap->nfrags;
      if (n > BDL_SIZE)
	{
	  cmn_err (CE_WARN, "Internal error - BDL too small\n");
	  return OSS_EIO;
	}

      for (i = 0; i < n; i++)
	{
	  int next = i + 1;
	  if (i == n - 1)
	    next = 0;

	  devc->playBDL[i].addr =
	    dmap->dmabuf_phys + (i * dmap->fragment_size);
	  devc->playBDL[i].status = 0;
	  devc->playBDL[i].size = dmap->fragment_size >> 2;
	  devc->playBDL[i].next = devc->playBDL_phys + next * sizeof (bdl_t);
	}
    }
  portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

static int
ATI_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  ATI_devc *devc = audio_engines[dev]->devc;
  ATI_portc *portc = audio_engines[dev]->portc;
  unsigned int f = 0;
  oss_native_word flags;
  int loop = 100;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  /* we need to read the regs a couple of times because of possible h/w bugs */
  while (loop--)
    {
      if (direction == PCM_ENABLE_OUTPUT)
	{
	  if (portc->port_type == DF_ANALOG)
	    f = READL (devc->osdev, 0x44);
	  else
	    f = READL (devc->osdev, 0x5c);
	}

      if (direction == PCM_ENABLE_INPUT)
	{
	  f = READL (devc->osdev, 0x2c);
	}

      if (f < dmap->dmabuf_phys)
	continue;

      f -= dmap->dmabuf_phys;

      if (f >= dmap->bytes_in_use)
	continue;

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

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

static const audiodrv_t ATI_audio_driver = {
  ATI_audio_open,
  ATI_audio_close,
  ATI_audio_output_block,
  ATI_audio_start_input,
  ATI_audio_ioctl,
  ATI_audio_prepare_for_input,
  ATI_audio_prepare_for_output,
  ATI_audio_reset,
  NULL,
  NULL,
  ATI_audio_reset_input,
  ATI_audio_reset_output,
  ATI_audio_trigger,
  ATI_audio_set_rate,
  ATI_audio_set_format,
  ATI_audio_set_channels,
  NULL,
  NULL,
  NULL,				/* ATI_check_input, */
  NULL,				/* ATI_check_output, */
  NULL,				/* ATI_alloc_buffer, */
  NULL,				/* ATI_free_buffer, */
  NULL,
  NULL,
  ATI_get_buffer_pointer
};

static int
init_ATI (ATI_devc * devc)
{
  int my_mixer, my_dev, opts;
  int i, timeout;
  oss_native_word phaddr;

  /* Power up */
  WRITEL (devc->osdev, READL (devc->osdev, 0x08) & ~1, 0x08);
  oss_udelay (20);
  /* check if the ACLink is Active */
  timeout = 10;
  while (!(READL (devc->osdev, 0x08) & (1 << 28)))
    {
      WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (1 << 30), 0x08);	/* assert sync */
      oss_udelay (10);
      if (READL (devc->osdev, 0x08) & (1 << 28))
	break;

      /* set AC97 reset field to 0 */
      WRITEL (devc->osdev, READL (devc->osdev, 0x08) & ~(0x80000000), 0x08);
      oss_udelay (10);

      /* set AC97 reset field to 1 */
      WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (0x80000000), 0x08);
      oss_udelay (10);

      if (READL (devc->osdev, 0x08) & (1 << 28))
	break;
      if (!--timeout)
	{
	  cmn_err (CE_WARN, "Timed out waiting for AC97 Link ready\n");
	  break;
	}
    }

  /* set ac97 reset field to 1: deassert */
  WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (0x80000000), 0x08);

  /* do a soft reset */
  WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (1 << 29), 0x08);
  oss_udelay (10);

  /* deassert softreset */
  WRITEL (devc->osdev, READL (devc->osdev, 0x08) & ~(1 << 29), 0x08);
  oss_udelay (10);

  /* do a sync */
  WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (1 << 30), 0x08);
  oss_udelay (10);

  /* now continue with initializing the rest of the chip */
  WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (1 << 25), 0x08);	/*enable burst */

  WRITEL (devc->osdev, 0x22, 0x04);	/* enable audio+spdif interrupts */
  devc->bdlBuffer =
    CONTIG_MALLOC (devc->osdev, MAX_PORTC * BDL_SIZE * sizeof (bdl_t),
		   MEMLIMIT_32BITS, &phaddr, devc->bldbuf_dma_handle);
  if (devc->bdlBuffer == NULL)
    {
      cmn_err (CE_WARN, "OSS Failed to allocate BDL\n");
      return 0;
    }

  devc->playBDL = (bdl_t *) devc->bdlBuffer;
  devc->playBDL_phys = phaddr;
  devc->recBDL =
    (bdl_t *) (devc->bdlBuffer + (1 * BDL_SIZE * sizeof (bdl_t)));
  devc->recBDL_phys = phaddr + (1 * BDL_SIZE * sizeof (bdl_t));
  devc->spdifBDL =
    (bdl_t *) (devc->bdlBuffer + (2 * BDL_SIZE * sizeof (bdl_t)));
  devc->spdifBDL_phys = phaddr + (2 * BDL_SIZE * sizeof (bdl_t));


Init mixer

  my_mixer =
    ac97_install_full (&devc->ac97devc, "AC97 Mixer", ac97_read, ac97_write,
		       devc, devc->osdev, devc->inverted);

  if (my_mixer == -1)
    return 0;			/* No mixer */

  devc->mixer_dev = my_mixer;


  /* enable S/PDIF */
  devc->ac97devc.spdif_slot = SPDIF_SLOT1011;
  ac97_spdifout_ctl (devc->mixer_dev, SPDIFOUT_ENABLE, SNDCTL_MIX_WRITE, 1);

  for (i = 0; i < MAX_PORTC; i++)
    {
      ATI_portc *portc = &devc->portc[i];
      char tmp_name[100];
      int formats = AFMT_S16_LE | AFMT_AC3;
      char *devfile_name = "";
      strcpy (tmp_name, devc->chip_name);
      opts = ADEV_AUTOMODE | ADEV_STEREOONLY | ADEV_FIXEDRATE;
      portc->port_type = DF_ANALOG;

      if (i == 0)
	{
	  opts |= ADEV_DUPLEX;
	  strcpy (tmp_name, devc->chip_name);
	}
      if (i == 1)
	{
	  strcpy (tmp_name, devc->chip_name);
	  opts |= ADEV_DUPLEX | ADEV_SHADOW;
	}
      if (i == 2)
	{
	  sprintf (tmp_name, "%s (SPDIF out)", devc->chip_name);
	  opts |= ADEV_NOINPUT | ADEV_SPECIAL;
	  portc->port_type = DF_SPDIF;
	  devfile_name = "spdout";
	}

      if ((my_dev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION,
					  devc->osdev,
					  devc->osdev,
					  tmp_name,
					  &ATI_audio_driver,
					  sizeof (audiodrv_t),
					  opts, formats, devc, -1,
					  devfile_name)) < 0)
	{
	  my_dev = -1;
	  return 0;
	}
      else
	{
	  audio_engines[my_dev]->portc = portc;
	  audio_engines[my_dev]->mixer_dev = my_mixer;
	  audio_engines[my_dev]->min_rate =
	    (opts & ADEV_FIXEDRATE) ? 48000 : 5000;
	  audio_engines[my_dev]->max_rate = 48000;
	  audio_engines[my_dev]->caps |= PCM_CAP_FREERATE;
	  audio_engines[my_dev]->min_channels = 2;
	  audio_engines[my_dev]->max_channels = 6;


We can have at most BDL_SIZE fragments and they can be at most 128k each.

	  audio_engines[my_dev]->max_block = 128 * 1024;
	  audio_engines[my_dev]->max_fragments = BDL_SIZE;
	  portc->open_mode = 0;
	  portc->audio_enabled = 0;
	  portc->audiodev = my_dev;
	  if (audio_engines[my_dev]->flags & ADEV_FIXEDRATE)
	    audio_engines[my_dev]->fixed_rate = 48000;
#ifdef CONFIG_OSS_VMIX
	  if (i == 0)
	     vmix_attach_audiodev(devc->osdev, my_dev, -1, 0);
#endif
	}
    }
  return 1;
}


int
oss_atiaudio_attach (oss_device_t * osdev)
{
  unsigned char pci_irq_line, pci_revision /*, pci_latency */ ;
  unsigned short pci_command, vendor, device, sub_vendor, sub_id;
  unsigned int pci_ioaddr;
  ATI_devc *devc;

  DDB (cmn_err (CE_WARN, "Entered ATI AC97 probe routine\n"));

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

  if ((vendor != ATI_VENDOR_ID) || (device != ATI_DEVICE_IXP &&
				    device != ATI_DEVICE_IXP300
				    && device != ATI_DEVICE_IXP400))
    return 0;

  oss_pci_byteswap (osdev, 1);

  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_dword (osdev, PCI_MEM_BASE_ADDRESS_0, &pci_ioaddr);

  if (pci_ioaddr == 0)
    {
      cmn_err (CE_WARN, "I/O address not assigned by BIOS.\n");
      return 0;
    }

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

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

  devc->osdev = osdev;
  osdev->devc = devc;

  pci_read_config_word (osdev, PCI_SUBSYSTEM_VENDOR_ID, &sub_vendor);
  pci_read_config_word (osdev, PCI_SUBSYSTEM_ID, &sub_id);
  switch ((sub_id << 16) | sub_vendor)
    {
      case 0x11831043:  /* ASUS A6R */
      case 0x2043161f:  /* Maxselect x710s */
      case 0x0506107b:  /* Gateway 7510GX */
	devc->inverted = AC97_INVERTED;
	cmn_err (CE_CONT, "An inverted amplifier has been autodetected\n");
	break;
      default:
	devc->inverted = 0;
	break;
    }

  devc->membar_addr = pci_ioaddr;
  devc->membar_virt =
    (char *) MAP_PCI_MEM (devc->osdev, 0, devc->membar_addr, 256);

  if (devc->membar_virt == NULL)
    {
      cmn_err (CE_WARN, "ATIIXP: Cannot map pci mem\n");
      return 0;
    }

  pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY;
  pci_write_config_word (osdev, PCI_COMMAND, pci_command);

#if 0
  latency = pci_read_config_dword (osdev, 0x50, &latency);
  pci_write_config_dword (osdev, 0x50, latency | 1);
#endif

  switch (device)
    {
    case ATI_DEVICE_IXP:
      devc->chip_name = "ATI IXP200";
      break;

    case ATI_DEVICE_IXP300:
      devc->chip_name = "ATI IXP300";
      break;

    case ATI_DEVICE_IXP400:
      devc->chip_name = "ATI IXP400";
      break;

    default:
      devc->chip_name = "ATI IXP";
      break;
    }

  devc->irq = pci_irq_line;
  devc->open_mode = 0;

  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 (oss_register_interrupts (devc->osdev, 0, ATIIXPintr, NULL) < 0)
    {
      cmn_err (CE_WARN, "Unable to register interrupts\n");
      return 0;
    }

  return init_ATI (devc);	/* Detected */
}


int
oss_atiaudio_detach (oss_device_t * osdev)
{
  ATI_devc *devc = (ATI_devc *) osdev->devc;

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

  WRITEL (devc->osdev, 0x3f, 0x00);	/* ack all interrupts */
  WRITEL (devc->osdev, 0, 0x04);	/* disable interrupts */

  /* disable S/PDIF */
  if (devc->mixer_dev)
    ac97_spdifout_ctl (devc->mixer_dev, SPDIFOUT_ENABLE, SNDCTL_MIX_WRITE, 0);

  oss_unregister_interrupts (devc->osdev);

  if (devc->bdlBuffer)
    CONTIG_FREE (devc->osdev, devc->bdlBuffer,
		 MAX_PORTC * BDL_SIZE * sizeof (bdl_t), devc->bldbuf_dma_handle);

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

  if (devc->membar_addr != 0)
    {
      UNMAP_PCI_MEM (devc->osdev, 0, devc->membar_addr, devc->membar_virt,
		     256);
      devc->membar_addr = 0;
    }

  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