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_cs4281/oss_cs4281.c

Driver for Crystal PCI 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_cs4281_cfg.h"
#include "midi_core.h"
#include "ac97.h"
#include "oss_pci.h"
#include "cs4281.h"

#define CRYSTAL_VENDOR_ID	0x1013
#define CRYSTAL_CS4281_ID	0x6005

#if 1
#define WRITEB(a,d) devc->bRegister0[a] = d
#define READB(a) devc->bRegister0[a]
#define WRITEW(a,d) devc->wRegister0[a>>1] = d
#define READW(a) devc->wRegister0[a>>1]
#define READL(a) (devc->dwRegister0[a>>2])
#define WRITEL(a, d) devc->dwRegister0[a>>2] = d
#else
#define WRITEB(a,d) PCI_WRITEB(devc->osdev, d, devc->bRegister0[a])
#define READB(a) PCI_READB(devc->osdev, devc->bRegister0[a])
#define WRITEW(a,d) PCI_WRITEW(devc->osdev, d, devc->wRegister0[a>>1])
#define READW(a) PCI_READW(devc->osdev, devc->wRegister0[a>>1])
#define READL(a) PCI_READL(devc->osdev, devc->dwRegister0[a>>2])
#define WRITEL(a, d) PCI_WRITEL(devc->osdev, d, devc->dwRegister0[a>>2])
#endif

#ifdef OSS_BIG_ENDIAN
static unsigned int
be_swap (unsigned int x)
{
  return ((x & 0x000000ff) << 24) |
    ((x & 0x0000ff00) << 8) |
    ((x & 0x00ff0000) >> 8) | ((x & 0xff000000) >> 24);
}

#define LSWAP(x) be_swap(x)
#else
#define LSWAP(x) 	x
#endif

#define MAX_PORTC 2

typedef struct cs4281_portc
{
  int speed, bits, channels;
  int open_mode;
  int trigger_bits;
  int audio_enabled;
  int audiodev;
}
cs4281_portc;

typedef struct cs4281_devc
{
  oss_device_t *osdev;
  char *chip_name;
  oss_native_word bar0addr, bar1addr;
  unsigned int *bar0virt, *bar1virt;
  volatile unsigned int *dwRegister0, *dwRegister1;
  volatile unsigned short *wRegister0, *wRegister1;
  volatile unsigned char *bRegister0, *bRegister1;
  int irq;
  volatile unsigned char intr_mask;
  oss_mutex_t mutex;
  oss_mutex_t low_mutex;

  /* MIDI */
  int midi_opened;
  int midi_dev;
  oss_midi_inputbyte_t midi_input_intr;

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

  /* Audio parameters */
  cs4281_portc portc[MAX_PORTC];
  int open_mode;
  int fm_attached, mpu_attached;
}
cs4281_devc;



static int
ac97_read (void *devc_, int reg)
{
  cs4281_devc *devc = devc_;
  int count;
  oss_native_word status, value;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

Make sure that there is not data sitting around from a previous uncompleted access. ACSDA = Status Data Register = 47Ch

  status = READL (BA0_ACSDA);
  /* Get the actual AC97 register from the offset */
  WRITEL (BA0_ACCAD, reg);
  WRITEL (BA0_ACCDA, 0);
  WRITEL (BA0_ACCTL, ACCTL_DCV | ACCTL_CRW | ACCTL_VFRM | ACCTL_ESYN);

  /* Wait for the read to occur. */
  for (count = 0; count < 500; count++)
    {
      /* First, we want to wait for a short time. */
      oss_udelay (10);

Now, check to see if the read has completed. ACCTL = 460h, DCV should be reset by now and 460h = 17h

      status = READL (BA0_ACCTL);
      if (!(status & ACCTL_DCV))
	{
	  break;
	}
    }

  /* Make sure the read completed. */
  if (status & ACCTL_DCV)
    {
      cmn_err (CE_WARN, "AC97 Read Timedout\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return (-1);
    }

  /* Wait for the valid status bit to go active. */

  for (count = 0; count < 500; count++)
    {

Read the AC97 status register. ACSTS = Status Register = 464h

      status = READL (BA0_ACSTS);

See if we have valid status. VSTS - Valid Status

      if (status & ACSTS_VSTS)
	break;


Wait for a short while.

      oss_udelay (10);
    }
  /* Make sure we got valid status. */
  if (!(status & ACSTS_VSTS))
    {
      cmn_err (CE_WARN, "AC97 Read Timedout\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return (-1);
    }


Read the data returned from the AC97 register. ACSDA = Status Data Register = 474h

  value = READL (BA0_ACSDA);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return (value);
}

static int
ac97_write (void *devc_, int reg, int data)
{
  cs4281_devc *devc = devc_;
  int count;
  oss_native_word status, flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  WRITEL (BA0_ACCAD, reg);
  WRITEL (BA0_ACCDA, data);
  WRITEL (BA0_ACCTL, ACCTL_DCV | ACCTL_VFRM | ACCTL_ESYN);
  for (count = 0; count < 500; count++)
    {
      /* First, we want to wait for a short time. */
      oss_udelay (10);
      /* Now, check to see if the write has completed. */
      /* ACCTL = 460h, DCV should be reset by now and 460h = 07h */
      status = READL (BA0_ACCTL);
      if (!(status & ACCTL_DCV))
	break;
    }

  /* write didn't completed. */
  if (status & ACCTL_DCV)
    {
      cmn_err (CE_WARN, "AC97 Write timeout\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return (-1);
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 0;
}


static int
cs4281intr (oss_device_t * osdev)
{
  cs4281_devc *devc = (cs4281_devc *) osdev->devc;
  cs4281_portc *portc;
  oss_native_word status, uart_stat;
  int i;
  int serviced = 0;

/* Read the Interrupt Status Register */
  status = READL (BA0_HISR);


This is the MIDI read interrupt service. First check to see if the MIDI interrupt flag is set in the HISR register. Next read the MIDI status register. See if Receive Buffer Empty is empty (0=FIFO Not empty, 1=FIFO is empty

  if ((devc->midi_opened & OPEN_READ) && (status & HISR_MIDI))
    {
      serviced = 1;
      uart_stat = READL (BA0_MIDSR);

read one byte of MIDI data and hand it off the the sequencer module to decode this. Keep checking to see if the data is available. Stop when no more data is there in the FIFO.

      while (!(uart_stat & MIDSR_RBE))
	{
	  unsigned char d;
	  d = READL (BA0_MIDRP);

	  if (devc->midi_opened & OPEN_READ && devc->midi_input_intr)
	    devc->midi_input_intr (devc->midi_dev, d);
	  uart_stat = READL (BA0_MIDSR);
	}
    }
/* Audio interrupt handling */
  if (status & (HISR_INTENA | HISR_DMAI))
    for (i = 0; i < MAX_PORTC; i++)
      {
	portc = &devc->portc[i];
	if ((status & HISR_DMA0) && (portc->trigger_bits & PCM_ENABLE_OUTPUT))
	  {
	    dmap_t *dmapout = audio_engines[portc->audiodev]->dmap_out;
	    unsigned int currdac;
	    int n;

	    serviced = 1;
	    READL (BA0_HDSR0);	/* ack the DMA interrupt */
	    currdac = READL (BA0_DCA0) - dmapout->dmabuf_phys;
	    currdac /= dmapout->fragment_size;
	    n = 0;
	    while (dmap_get_qhead (dmapout) != currdac
		   && n++ < dmapout->nfrags)
	      oss_audio_outputintr (portc->audiodev, 1);
	  }

	if ((status & HISR_DMA1) && (portc->trigger_bits & PCM_ENABLE_INPUT))
	  {
	    dmap_t *dmapin = audio_engines[portc->audiodev]->dmap_in;
	    unsigned int curradc;
	    int n;

	    serviced = 1;
	    READL (BA0_HDSR1);	/* ack the DMA interrupt */
	    curradc = READL (BA0_DCA1) - dmapin->dmabuf_phys;
	    curradc /= dmapin->fragment_size;
	    n = 0;
	    while (dmap_get_qtail (dmapin) != curradc && n++ < dmapin->nfrags)
	      oss_audio_inputintr (portc->audiodev, 0);
	  }
	WRITEL (BA0_HICR, HICR_IEV | HICR_CHGM);
      }
  return serviced;
}

static int
cs4281_audio_set_rate (int dev, int arg)
{
  cs4281_portc *portc = audio_engines[dev]->portc;
  if (arg == 0)
    return portc->speed;
  if (arg > 48000)
    arg = 48000;
  if (arg < 6023)
    arg = 6023;
  portc->speed = arg;
  return portc->speed;
}

static short
cs4281_audio_set_channels (int dev, short arg)
{
  cs4281_portc *portc = audio_engines[dev]->portc;
  if ((arg != 1) && (arg != 2))
    return portc->channels;
  portc->channels = arg;
  return portc->channels;
}

static unsigned int
cs4281_audio_set_format (int dev, unsigned int arg)
{
  cs4281_portc *portc = audio_engines[dev]->portc;
  if (arg == 0)
    return portc->bits;
  if (!(arg & (AFMT_U8 | AFMT_S16_LE)))
    return portc->bits;
  portc->bits = arg;
  return portc->bits;
}

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

static void cs4281_audio_trigger (int dev, int state);
static void
cs4281_audio_reset (int dev)
{
  cs4281_audio_trigger (dev, 0);
}

static void
cs4281_audio_reset_input (int dev)
{
  cs4281_portc *portc = audio_engines[dev]->portc;
  cs4281_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
cs4281_audio_reset_output (int dev)
{
  cs4281_portc *portc = audio_engines[dev]->portc;
  cs4281_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

/*ARGSUSED*/
static int
cs4281_audio_open (int dev, int mode, int open_flags)
{
  cs4281_portc *portc = audio_engines[dev]->portc;
  cs4281_devc *devc = audio_engines[dev]->devc;
  oss_native_word flags;
  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  if (portc->open_mode)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return OSS_EBUSY;
    }

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

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

static void
cs4281_audio_close (int dev, int mode)
{
  cs4281_portc *portc = audio_engines[dev]->portc;
  cs4281_devc *devc = audio_engines[dev]->devc;
  cs4281_audio_reset (dev);
  portc->open_mode = 0;
  devc->open_mode &= ~mode;
  portc->audio_enabled &= ~mode;
}

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

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

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

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

static void
cs4281_audio_trigger (int dev, int state)
{
  cs4281_devc *devc = audio_engines[dev]->devc;
  cs4281_portc *portc = audio_engines[dev]->portc;
  oss_native_word tmp1;
  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))
	    {
	      /* Clear DMA0 channel Mask bit. Start Playing. */
	      tmp1 = READL (BA0_DCR0) & ~DCRn_MSK;	/*enable DMA */
	      WRITEL (BA0_DCR0, tmp1);	/* (154h) */
	      WRITEL (BA0_HICR, HICR_IEV | HICR_CHGM);	/*enable intr */
	      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;
	      tmp1 = READL (BA0_DCR0) & ~DCRn_MSK;
	      WRITEL (BA0_DCR0, tmp1 | DCRn_MSK);
	    }
	}
    }
  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      /* Clear DMA1 channel Mask bit. Start recording. */
	      tmp1 = READL (BA0_DCR1) & ~DCRn_MSK;
	      WRITEL (BA0_DCR1, tmp1);	/* (15ch) */
	      WRITEL (BA0_HICR, HICR_IEV | HICR_CHGM);	/*Set INTENA=1. */
	      portc->trigger_bits |= PCM_ENABLE_INPUT;
	    }
	}
      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;
	      tmp1 = READL (BA0_DCR1) & ~DCRn_MSK;
	      WRITEL (BA0_DCR1, tmp1 | DCRn_MSK);
	    }
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

static int
cs4281_rate (oss_native_word rate)
{
  int val = 0;

  switch (rate)
    {
    case 8000:
      val = 5;
      break;
    case 11025:
      val = 4;
      break;
    case 16000:
      val = 3;
      break;
    case 22050:
      val = 2;
      break;
    case 44100:
      val = 1;
      break;
    case 48000:
      val = 0;
      break;
    default:
      val = 1536000 / rate;
      break;
    }
  return val;
}

/*ARGSUSED*/
static int
cs4281_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  cs4281_devc *devc = audio_engines[dev]->devc;
  cs4281_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  int count = dmap->bytes_in_use;
  oss_native_word recordFormat;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  /* set the record rate */
  WRITEL (BA0_ADCSR, cs4281_rate (portc->speed));	/* (748h) */
  /* Start with defaults for the record format */
  /* reg & modify them for the current case. */
  recordFormat = DMRn_DMA | DMRn_AUTO | DMRn_TR_WRITE;
  if (portc->channels == 1)	/* If mono, */
    recordFormat |= DMRn_MONO;	/* Turn on mono bit. */
  if (portc->bits == 8)		/* If 8-bit, */
    recordFormat |= (DMRn_SIZE8 | DMRn_USIGN);	/* turn on 8bit/unsigned. */
  WRITEL (BA0_DMR1, recordFormat);
  /* set input gain to 0db */
  /* ac97_write(devc, BA0_AC97_RECORD_GAIN, 0x0808);  */
  if (portc->channels == 2)	/* If stereo, */
    count /= 2;			/* halve DMA count(stereo); */
  if (portc->bits == 16)	/* If 16-bit, */
    count /= 2;
  /* Set the physical play buffer address DMA1 Base & Current. */
  WRITEL (BA0_DBA1, dmap->dmabuf_phys);	/* (118h) */
  /* Set the sample count(-1) in DMA Base Count register 1. */
  WRITEL (BA0_DBC1, count - 1);
  portc->audio_enabled &= ~PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

/*ARGSUSED*/
static int
cs4281_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  cs4281_devc *devc = audio_engines[dev]->devc;
  cs4281_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_out;
  int count = dmap->bytes_in_use;
  oss_native_word playFormat;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  /* Set the sample rate converter */
  WRITEL (BA0_DACSR, cs4281_rate (portc->speed));
  playFormat = DMRn_DMA | DMRn_AUTO | DMRn_TR_READ | (1 << 6);
  if (portc->channels == 1)	/* If stereo, */
    playFormat |= DMRn_MONO;	/* Turn on mono bit. */
  if (portc->bits == 8)		/* If 16-bit, */
    playFormat |= (DMRn_SIZE8 | DMRn_USIGN);	/* turn on 8-bit/unsigned. */
  WRITEL (BA0_DMR0, playFormat);	/* (150h) */
  if (portc->channels == 2)	/* If stereo, */
    count /= 2;			/* halve DMA count(stereo); */
  if (portc->bits == 16)	/* If 16-bit, */
    count /= 2;
  /* Set the physical play buffer address DMA0 Base & Current. */
  WRITEL (BA0_DBA0, dmap->dmabuf_phys & ~0x3);	/* (118h) */
  /* Set the sample count(-1) in DMA Base Count register 0. */
  WRITEL (BA0_DBC0, count - 1);
  portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

static int
cs4281_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  cs4281_devc *devc = audio_engines[dev]->devc;
  unsigned int ptr = 0;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

  if (direction == PCM_ENABLE_OUTPUT)
    {
      ptr = READL (BA0_DCA0);
    }
  if (direction == PCM_ENABLE_INPUT)
    {
      ptr = READL (BA0_DCA1);
    }
  ptr -= dmap->dmabuf_phys;
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return ptr;
}

static audiodrv_t cs4281_audio_driver = {
  cs4281_audio_open,
  cs4281_audio_close,
  cs4281_audio_output_block,
  cs4281_audio_start_input,
  cs4281_audio_ioctl,
  cs4281_audio_prepare_for_input,
  cs4281_audio_prepare_for_output,
  cs4281_audio_reset,
  NULL,
  NULL,
  cs4281_audio_reset_input,
  cs4281_audio_reset_output,
  cs4281_audio_trigger,
  cs4281_audio_set_rate,
  cs4281_audio_set_format,
  cs4281_audio_set_channels,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,				/* cs4281_alloc_buffer, */
  NULL,				/* cs4281_free_buffer, */
  NULL,
  NULL,
  cs4281_get_buffer_pointer
};

/***********************MIDI PORT ROUTINES ****************/
/*ARGSUSED*/
static int
cs4281_midi_open (int dev, int mode, oss_midi_inputbyte_t inputbyte,
		  oss_midi_inputbuf_t inputbuf,
		  oss_midi_outputintr_t outputintr)
{
  cs4281_devc *devc = (cs4281_devc *) midi_devs[dev]->devc;
  if (devc->midi_opened)
    {
      return OSS_EBUSY;
    }

  devc->midi_input_intr = inputbyte;
  devc->midi_opened = mode;
  /* first reset the MIDI port */
  WRITEL (BA0_MIDCR, 0x10);
  WRITEL (BA0_MIDCR, 0x00);
  /* Now check if we're in Read or Write mode */
  if (mode & OPEN_READ)
    {
      /* enable MIDI Input intr and receive enable */
      WRITEL (BA0_MIDCR, MIDCR_RXE | MIDCR_RIE);
    }

  if (mode & OPEN_WRITE)
    {
      /* enable MIDI transmit enable */
      WRITEL (BA0_MIDCR, MIDCR_TXE);
    }
  return 0;
}

/*ARGSUSED*/
static void
cs4281_midi_close (int dev, int mode)
{
  cs4281_devc *devc = (cs4281_devc *) midi_devs[dev]->devc;
/* Reset the device*/
  WRITEL (BA0_MIDCR, 0x10);
  WRITEL (BA0_MIDCR, 0x00);
  devc->midi_opened = 0;
}

static int
cs4281_midi_out (int dev, unsigned char midi_byte)
{
  cs4281_devc *devc = (cs4281_devc *) midi_devs[dev]->devc;
  unsigned char uart_stat = READL (BA0_MIDSR);
/* Check if Transmit buffer full flag is set - if so return */
  if ((uart_stat & MIDSR_TBF))
    return 0;
/* Now put the MIDI databyte in the write port */
  WRITEL (BA0_MIDWP, midi_byte);
  return 1;
}

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

static midi_driver_t cs4281_midi_driver = {
  cs4281_midi_open,
  cs4281_midi_close,
  cs4281_midi_ioctl,
  cs4281_midi_out
};

static int
init_cs4281 (cs4281_devc * devc)
{

  int my_mixer, i;
  oss_native_word dwAC97SlotID, tmp1;
  int first_dev = 0;
/****************BEGIN HARDWARE INIT*****************/
  /* Setup CFLR */
  tmp1 = READL (BA0_CFLR);
  if (tmp1 != 0x01)
    {
      WRITEL (BA0_CFLR, 0x01);	/*set up AC97 mode */
      tmp1 = READL (BA0_CFLR);
      if (tmp1 != 0x01)
	{
	  tmp1 = READL (BA0_CWPR);
	  if (tmp1 != 0x4281)
	    WRITEL (BA0_CWPR, 0x4281);
	  tmp1 = READL (BA0_CWPR);
	  if (tmp1 != 0x4281)
	    {
	      cmn_err (CE_WARN, "Resetting AC97 failed\n");
	      return OSS_EIO;
	    }
	  WRITEL (BA0_CFLR, 0x1);
	  tmp1 = READL (BA0_CFLR);
	  if (tmp1 != 0x1)
	    {
	      cmn_err (CE_WARN, "Resetting AC97 still fails\n");
	      return OSS_EIO;
	    }
	}
    }
  /* Setup the FM and Joystick trap address for Legacy emulation */
  WRITEL (BA0_IOTCR, 0x1);
  WRITEL (BA0_IOTFM, 0xc0030388);
  WRITEL (BA0_IOTGP, 0xc0070200);
  /**************************************** */
  /*  Set up the Sound System Configuration */
  /**************************************** */
  /* Set the 'Configuration Write Protect' register */
  /* to 4281h.  Allows vendor-defined configuration */
  /* space between 0e4h and 0ffh to be written. */
  WRITEL (BA0_CWPR, 0x4281);	/* (3e0h) */
  if ((tmp1 = READL (BA0_SERC1)) != (SERC1_SO1EN | SERC1_SO1F_AC97))
    {
      cmn_err (CE_WARN, "SERC1: AC97 check failed\n");
      return OSS_EIO;
    }
  /* setup power management to full power */
  WRITEL (BA0_SSPM, 0x7E);
  /* First, blast the clock control register to zero so that the */
  /* PLL starts out in a known state, and blast the master serial */
  /* port control register to zero so that the serial ports also */
  /* start out in a known state. */
  WRITEL (BA0_CLKCR1, 0);	/* (400h) */
  WRITEL (BA0_SERMC, 0);	/* (420h) */
  /* (1) Drive the ARST# pin low for a minimum of 1uS (as defined in */
  /* the AC97 spec) and then drive it high.  This is done for non */
  /* AC97 modes since there might be logic external to the CS461x */
  /* that uses the ARST# line for a reset. */
  WRITEL (BA0_ACCTL, 0);
  oss_udelay (50);
  WRITEL (BA0_SPMC, 0);		/* (3ech) */
  oss_udelay (50);
  WRITEL (BA0_SPMC, SPMC_RSTN);
  oss_udelay (50000);
  WRITEL (BA0_SERMC, SERMC_PTC_AC97 | SERMC_MSPE | 0x10000);
  /* (3) Turn on the Sound System Clocks. */
  WRITEL (BA0_CLKCR1, CLKCR1_DLLP);	/* (400h) */
  /* Wait for the PLL to stabilize. */
  oss_udelay (50000);
  /* Turn on clocking of the core (CLKCR1(400h) = 0x00000030) */
  WRITEL (BA0_CLKCR1, CLKCR1_DLLP | CLKCR1_SWCE);
  /* (5) Wait for clock stabilization. */
  for (tmp1 = 0; tmp1 < 100; tmp1++)
    {
      if (READL (BA0_CLKCR1) & CLKCR1_DLLRDY)
	break;
      oss_udelay (50000);
    }
  if (!(READL (BA0_CLKCR1) & CLKCR1_DLLRDY))
    {
      cmn_err (CE_WARN, "DLLRDY Clock not ready\n");
      return OSS_EIO;
    }
  /* (6) Enable ASYNC generation. */
  WRITEL (BA0_ACCTL, ACCTL_ESYN);	/* (460h) */
  /* Now wait 'for a short while' to allow the  AC97 */
  /* part to start generating bit clock. (so we don't */
  /* Try to start the PLL without an input clock.) */
  /* (7) Wait for the codec ready signal from the AC97 codec. */
  for (tmp1 = 0; tmp1 < 100; tmp1++)
    {
      /* Delay a mil to let things settle out and */
      /* to prevent retrying the read too quickly. */
      if (READL (BA0_ACSTS) & ACSTS_CRDY)	/* If ready,  (464h) */
	break;			/*   exit the 'for' loop. */
      oss_udelay (50000);
    }
  if (!(READL (BA0_ACSTS) & ACSTS_CRDY))	/* If never came ready, */
    {
      cmn_err (CE_WARN, "AC97 not ready\n");
      return OSS_EIO;		/*   exit initialization. */
    }
  /* (8) Assert the 'valid frame' signal so we can */
  /* begin sending commands to the AC97 codec. */
  WRITEL (BA0_ACCTL, ACCTL_VFRM | ACCTL_ESYN);	/* (460h) */

  /* (11) Wait until we've sampled input slots 3 & 4 as valid, meaning */
  /* that the codec is pumping ADC data across the AC link. */
  for (tmp1 = 0; tmp1 < 100; tmp1++)
    {
      /* Read the input slot valid register;  See */
      /* if input slots 3 and 4 are valid yet. */
      if ((READL (BA0_ACISV) & (ACISV_ISV3 | ACISV_ISV4)) ==	/* (474h) */
	  (ACISV_ISV3 | ACISV_ISV4))
	break;			/* Exit the 'for' if slots are valid. */
      oss_udelay (50000);
    }
  /* If we never got valid data, exit initialization. */
  if ((READL (BA0_ACISV) & (ACISV_ISV3 | ACISV_ISV4)) !=
      (ACISV_ISV3 | ACISV_ISV4))
    {
      cmn_err (CE_WARN, "AC97 Slot not valid\n");
      return OSS_EIO;		/* If no valid data, exit initialization. */
    }
  /* (12) Start digital data transfer of audio data to the codec. */
  WRITEL (BA0_ACOSV, ACOSV_SLV3 | ACOSV_SLV4);	/* (468h) */

  /* For playback, we map AC97 slot 3 and 4(Left */
  /* & Right PCM playback) to DMA Channel 0. */
  /* Set the fifo to be 31 bytes at offset zero. */
  dwAC97SlotID = 0x01001F00;	/* FCR0.RS[4:0]=1(=>slot4, right PCM playback). */
  /* FCR0.LS[4:0]=0(=>slot3, left PCM playback). */
  /* FCR0.SZ[6-0]=15; FCR0.OF[6-0]=0. */
  WRITEL (BA0_FCR0, dwAC97SlotID);	/* (180h) */
  WRITEL (BA0_FCR0, dwAC97SlotID | FCRn_FEN);	/* Turn on FIFO Enable. */
  /* For capture, we map AC97 slot 10 and 11(Left */
  /* and Right PCM Record) to DMA Channel 1. */
  /* Set the fifo to be 31 bytes at offset 32. */
  dwAC97SlotID = 0x0B0A1F20;	/* FCR1.RS[4:0]=11(=>slot11, right PCM record). */
  /* FCR1.LS[4:0]=10(=>slot10, left PCM record). */
  /* FCR1.SZ[6-0]=15; FCR1.OF[6-0]=16. */
  WRITEL (BA0_FCR1, dwAC97SlotID);	/* (184h) */
  WRITEL (BA0_FCR1, dwAC97SlotID | FCRn_FEN);	/* Turn on FIFO Enable. */
  /* Map the Playback SRC to the same AC97 slots(3 & 4-- */
  /* --Playback left & right)as DMA channel 0. */
  /* Map the record SRC to the same AC97 slots(10 & 11-- */
  /* -- Record left & right) as DMA channel 1. */
  dwAC97SlotID = 0x0b0a0100;	/*SCRSA.PRSS[4:0]=1(=>slot4, right PCM playback). */
  /*SCRSA.PLSS[4:0]=0(=>slot3, left PCM playback). */
  /*SCRSA.CRSS[4:0]=11(=>slot11, right PCM record) */
  /*SCRSA.CLSS[4:0]=10(=>slot10, left PCM record). */
  WRITEL (BA0_SRCSA, dwAC97SlotID);	/* (75ch) */
  /* Set 'Half Terminal Count Interrupt Enable' and 'Terminal */
  /* Count Interrupt Enable' in DMA Control Registers 0 & 1. */
  /* Set 'MSK' flag to 1 to keep the DMA engines paused. */
  tmp1 = (DCRn_HTCIE | DCRn_TCIE | DCRn_MSK);	/* (00030001h) */
  WRITEL (BA0_DCR0, tmp1);	/* (154h) */
  WRITEL (BA0_DCR1, tmp1);	/* (15ch) */
  /* Set 'Auto-Initialize Control' to 'enabled'; For playback, */
  /* set 'Transfer Type Control'(TR[1:0]) to 'read transfer', */
  /* for record, set Transfer Type Control to 'write transfer'. */
  /* All other bits set to zero;  Some will be changed @ transfer start. */
  tmp1 = (DMRn_DMA | DMRn_AUTO | DMRn_TR_READ);	/* (20000018h) */
  WRITEL (BA0_DMR0, tmp1);	/* (150h) */
  tmp1 = (DMRn_DMA | DMRn_AUTO | DMRn_TR_WRITE);	/* (20000014h) */
  WRITEL (BA0_DMR1, tmp1);	/* (158h) */
  /* Enable DMA interrupts generally, and */
  /* DMA0 & DMA1 interrupts specifically. */
  tmp1 = READL (BA0_HIMR) & 0x7fbbfcff;
  WRITEL (BA0_HIMR, tmp1);
  /* set up some volume defaults */
  WRITEL (BA0_PPLVC, 0x0808);
  WRITEL (BA0_PPRVC, 0x0808);
  WRITEL (BA0_FMLVC, 0x0);
  WRITEL (BA0_FMRVC, 0x0);
/****** END OF HARDWARE INIT *****/


  my_mixer =
    ac97_install (&devc->ac97devc, "CS4281 AC97 Mixer", ac97_read, ac97_write,
		  devc, devc->osdev);
  if (my_mixer < 0)
    {
      return 0;
    }

  devc->mixer_dev = my_mixer;

  /* ac97_write(devc,  0x26, ac97_read(devc, 0x26) | 0x8000); */
  for (i = 0; i < MAX_PORTC; i++)
    {
      int adev;
      int caps = 0;
      cs4281_portc *portc = &devc->portc[i];
      char tmp_name[100];
      strcpy (tmp_name, devc->chip_name);

      if (i == 0)
	{
          caps = ADEV_AUTOMODE | ADEV_DUPLEX;
	  strcpy (tmp_name, devc->chip_name);
	}
      else
	{
          caps = ADEV_AUTOMODE | ADEV_DUPLEX | ADEV_SHADOW;
	  strcpy (tmp_name, devc->chip_name);
	}

      if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					devc->osdev,
					devc->osdev,
					tmp_name,
					&cs4281_audio_driver,
					sizeof (audiodrv_t),
					caps,
					AFMT_S16_LE | AFMT_U8, devc, -1)) < 0)
	{
	  adev = -1;
	  return 0;
	}
      else
	{
	  if (i == 0)
	    first_dev = adev;
	  audio_engines[adev]->portc = portc;
	  audio_engines[adev]->rate_source = first_dev;
	  audio_engines[adev]->mixer_dev = my_mixer;
	  audio_engines[adev]->min_rate = 6023;
	  audio_engines[adev]->max_rate = 48000;
	  audio_engines[adev]->caps |= PCM_CAP_FREERATE;
	  portc->open_mode = 0;
	  portc->audiodev = adev;
	  portc->audio_enabled = 0;
#ifdef CONFIG_OSS_VMIX
	  if (i == 0)
	     vmix_attach_audiodev(devc->osdev, adev, -1, 0);
#endif
	}
    }

  devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "CS4281", "CS4281 MIDI Port", &cs4281_midi_driver, sizeof (midi_driver_t),
					0, devc, devc->osdev);
  devc->midi_opened = 0;
  return 1;
}

int
oss_cs4281_attach (oss_device_t * osdev)
{
  unsigned char pci_irq_line, pci_revision, pci_irq_inta;
  unsigned short pci_command, vendor, device;
  unsigned int ioaddr;
  int err;
  cs4281_devc *devc;

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

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

  if (vendor != CRYSTAL_VENDOR_ID || device != CRYSTAL_CS4281_ID)
    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;

  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_byte (osdev, PCI_INTERRUPT_LINE + 1, &pci_irq_inta);
  pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_0, &ioaddr);
  devc->bar0addr = ioaddr;
  pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_1, &ioaddr);
  devc->bar1addr = ioaddr;

  /* activate the device enable bus master/memory space */
  pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY;
  pci_write_config_word (osdev, PCI_COMMAND, pci_command);

  if ((devc->bar0addr == 0) || (devc->bar1addr == 0))
    {
      cmn_err (CE_WARN, "undefined MEMORY I/O address.\n");
      return 0;
    }

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


  /* Map the shared memory area */
  devc->bar0virt =
    (unsigned int *) MAP_PCI_MEM (devc->osdev, 0, devc->bar0addr, 4 * 1024);
  devc->bar1virt =
    (unsigned int *) MAP_PCI_MEM (devc->osdev, 1, devc->bar1addr,
				  1024 * 1024);
  devc->dwRegister0 = devc->bar0virt;
  devc->wRegister0 = (unsigned short *) devc->bar0virt;
  devc->bRegister0 = (unsigned char *) devc->bar0virt;
  devc->dwRegister1 = devc->bar1virt;
  devc->wRegister1 = (unsigned short *) devc->bar1virt;
  devc->bRegister1 = (unsigned char *) devc->bar1virt;

  devc->chip_name = "CS4281";
  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 ((err = oss_register_interrupts (devc->osdev, 0, cs4281intr, NULL)) < 0)
    {
      cmn_err (CE_WARN, "Can't register interrupt handler, err=%d\n", err);
      return 0;
    }
  return init_cs4281 (devc);	/*Detected */
}


int
oss_cs4281_detach (oss_device_t * osdev)
{
  cs4281_devc *devc = (cs4281_devc *) osdev->devc;

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

  WRITEL (BA0_HICR, 0x02);	/*enable intena */

  oss_unregister_interrupts (devc->osdev);

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

  UNMAP_PCI_MEM (devc->osdev, 0, devc->bar0addr, devc->bar0virt, 4 * 1024);
  UNMAP_PCI_MEM (devc->osdev, 1, devc->bar1addr, devc->bar1virt, 1024 * 1024);

  devc->bar0addr = 0;
  devc->bar1addr = 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