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_audigyls/oss_audigyls.c

Driver for Creative Audigy LS audio controller

Description



This sound card has been sold under many different names.


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_audigyls_cfg.h"
#include "oss_pci.h"
#include "ac97.h"
#include "midi_core.h"
#include "remux.h"

#define DEFAULT_RATE 48000

#undef  USE_ITIMER

#define PCI_VENDOR_ID_CREATIVE 		0x1102
#define PCI_DEVICE_ID_CREATIVE_AUDIGYLS 0x0007


Indirect registers


#define PTBA	0x000
#define PTBS	0x001
#define PTCA	0x002
#define PFBA	0x004
#define PFBS	0x005
#define CPFA	0x006
#define PFEA	0x007
#define CPCAV	0x008
#define RFBA	0x010
#define RFBS	0x011
#define CRFA	0x012
#define CRCAV	0x013
#define CDL	0x020
#define SA	0x040
#define SCS3	0x041
#define SCS0	0x042
#define SCS1	0x043
#define SCS2	0x044
#define SPC	0x045
#define WMARK	0x046
#define SPSC	0x049
#define RCD	0x050		/* 0x50-0z5f */
#define P17RECSEL       0x060
#define P17RECVOLL      0x061
#define P17RECVOLH      0x062

#define HMIXMAP_SPDIF 0x63
#define SMIXMAP_SPDIF 0x64
#define MIXCTL_SPDIF 0x65
#define MIXVOL_SPDIF 0x66
#define HMIXMAP_I2S 0x67
#define SMIXMAP_I2S 0x68
#define MIXCTL_I2S 0x69
#define MIXVOL_I2S 0x6a

/* MIDI UART */
#define MUDATA	0x06c
#define MUCMDA	0x06d
#define MUDATB	0x06e
#define MUCMDB	0x06f

#define SRT	0x070
#define SRCTL	0x071
#define AUDCTL	0x072
#define CHIP_ID	0x074
#define AINT_ENABLE	0x075
#define AINT_STATUS	0x076
#define Wall192		0x077
#define Wall441		0x078
#define IT		0x079
#define SPI		0x07a
#define I2C_A		0x07b
#define I2C_0		0x07c
#define I2C_1		0x07d


Global Interrupt bits


#define IE 0x0c
#define INTR_PCI	(1<<  0)
#define INTR_TXA	(1<<  1)
#define INTR_RXA	(1<<  2)
#define INTR_IT1	(1<<  3)
#define INTR_IT2	(1<<  4)
#define INTR_SS_	(1<<  5)
#define INTR_SRT	(1<<  6)
#define INTR_GP 	(1<<  7)
#define INTR_AI 	(1<<  8)
#define INTR_I2Cdac 	(1<<  9)
#define INTR_I2CEE 	(1<< 10)
#define INTR_SPI 	(1<< 11)
#define INTR_SPF 	(1<< 12)
#define INTR_SUO 	(1<< 13)
#define INTR_SUI 	(1<< 14)
#define INTR_TXB 	(1<< 16)
#define INTR_RXB 	(1<< 17)


Audio interrupt bits


#define AI_PFH		(1<<  0)
#define AI_PFF		(1<<  4)
#define AI_TFH		(1<<  8)
#define AI_TFF		(1<< 12)
#define AI_RFH		(1<< 16)
#define AI_RFF		(1<< 20)
#define AI_EAI		(1<< 24)


#define MAX_PORTC	3

extern int audigyls_spdif_enable;

typedef struct
{
  int audio_dev;
  int play_port;
  int rec_port;
  int open_mode;
  int trigger_bits;
  int audio_enabled;
  int channels;
  int bits;
  int speed;
#ifdef USE_ITIMER
  int frag_192khz;
#endif
  int port_type;
  int play_ptr, rec_ptr;
}
audigyls_portc;

typedef struct
{
  oss_device_t *osdev;
  oss_native_word base;
  oss_mutex_t mutex;
  oss_mutex_t low_mutex;
  char *card_name;
  unsigned int subvendor;
  int rec_src;			/* record channel src spdif/i2s/ac97/src */
#define RECSEL_SPDIFOUT   0
#define RECSEL_I2SOUT     1
#define RECSEL_SPDIFIN    2
#define RECSEL_I2SIN      3
#define RECSEL_AC97       4
#define RECSEL_SRC        5

  int mixer_dev;
  ac97_devc ac97devc;
  int has_ac97;
  int spread;			/* copy front to surr/center channels */
  int loopback;			/* record channel input from /dev/dspXX */
  int input_source;		/* input from mic/line/aux/etc */
  int captmon;         /* hear what you record*/ 
  int fbvol;         /* recording monitor volume */ 

UART

  oss_midi_inputbyte_t midi_input_intr;
  int midi_opened, midi_disabled;
  volatile unsigned char input_byte;
  int midi_dev;
  int mpu_attached;
  int playvol[4];
  int recvol;
  audigyls_portc portc[MAX_PORTC];

}
audigyls_devc;


static void audigylsuartintr (audigyls_devc * devc);

static unsigned int
read_reg (audigyls_devc * devc, int reg, int chn)
{
  oss_native_word flags;
  unsigned int val;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  OUTL (devc->osdev, (reg << 16) | (chn & 0xffff), devc->base + 0x00);	/* Pointer */
  val = INL (devc->osdev, devc->base + 0x04);	/* Data */
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

/*printk("Read reg %03x (ch %d) = %08x\n", reg, chn, val);*/
  return val;
}

static void
write_reg (audigyls_devc * devc, int reg, int chn, unsigned int value)
{
  oss_native_word flags;

  /*printk("Write reg %03x (ch %d) = %08x\n", reg, chn, value); */
  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  OUTL (devc->osdev, (reg << 16) | (chn & 0xffff), devc->base + 0x00);	/* Pointer */
  OUTL (devc->osdev, value, devc->base + 0x04);	/* Data */
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
#if 0
  {
    char tmp[100];
    int ok = 1;
    sprintf (tmp, "@w%d %04x/%s %x", chn, reg, emu_regname (reg, &ok), value);
    if (ok)
      oss_do_timing (tmp);
  }
#endif
}


static int
audigyls_ac97_read (void *devc_, int wAddr)
{
  audigyls_devc *devc = devc_;
  int dtemp = 0, i;

  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  OUTB (devc->osdev, wAddr, devc->base + 0x1e);
  for (i = 0; i < 10000; i++)
    if (INB (devc->osdev, devc->base + 0x1e) & 0x80)
      break;
  if (i == 10000)
    {
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return OSS_EIO;
    }
  dtemp = INW (devc->osdev, devc->base + 0x1c);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

  return dtemp & 0xffff;
}

static int
audigyls_ac97_write (void *devc_, int wAddr, int wData)
{
  audigyls_devc *devc = devc_;
  oss_native_word flags;
  int i;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  OUTB (devc->osdev, wAddr, devc->base + 0x1e);
  for (i = 0; i < 10000; i++)
    if (INB (devc->osdev, devc->base + 0x1e) & 0x80)
      break;
  if (i == 10000)
    {
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return OSS_EIO;
    }
  OUTW (devc->osdev, wData, devc->base + 0x1c);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

  return 0;
}

static void
check_recording_intr (audigyls_devc * devc, audigyls_portc * portc)
{
#if 1
  int pos, n = 0;
  dmap_p dmap;

  dmap = audio_engines[portc->audio_dev]->dmap_in;

  pos = read_reg (devc, CRFA, portc->rec_port);
  pos /= dmap->fragment_size;
  while (dmap_get_qtail (dmap) != pos && n++ < dmap->nfrags)
#endif
    oss_audio_inputintr (portc->audio_dev, 0);
}

static void
check_playback_intr (audigyls_devc * devc, audigyls_portc * portc)
{
#if 1
  int pos, n = 0;
  dmap_p dmap;

  dmap = audio_engines[portc->audio_dev]->dmap_out;

  pos = read_reg (devc, CPFA, portc->play_port);
  pos /= dmap->fragment_size;
  while (dmap_get_qhead (dmap) != pos && n++ < dmap->nfrags)
#endif
    oss_audio_outputintr (portc->audio_dev, 0);
}

static int
audigylsintr (oss_device_t * osdev)
{
  int serviced = 0;
  unsigned int status;
  unsigned int astatus = 0;
  int i;
  audigyls_devc *devc = osdev->devc;
  audigyls_portc *portc;
  oss_native_word flags;

  flags = 0;			/* To fix compiler warnings about unused variable */
  MUTEX_ENTER (devc->mutex, flags);

  status = INL (devc->osdev, devc->base + 0x08);

  if (status & INTR_RXA)	/* MIDI RX interrupt */
    {
      audigylsuartintr (devc);
    }

  if (status & INTR_AI)
    {
      astatus = read_reg (devc, AINT_STATUS, 0);
      for (i = 0; i < MAX_PORTC; i++)
	{
	  portc = &devc->portc[i];

	  if ((portc->trigger_bits & PCM_ENABLE_OUTPUT) &&
	      (astatus & ((AI_PFF | AI_PFH) << portc->play_port)))
	    {
	      dmap_t *dmap = audio_engines[portc->audio_dev]->dmap_out;

	      if (astatus & (AI_PFF << portc->play_port))
		portc->play_ptr = 0;
	      if (astatus & (AI_PFH << portc->play_port))
		portc->play_ptr = dmap->bytes_in_use / 2;

	      oss_audio_outputintr (portc->audio_dev, 0);
	    }
	  if ((portc->trigger_bits & PCM_ENABLE_INPUT) &&
	      (astatus & ((AI_RFF | AI_RFH) << portc->rec_port)))
	    {
	      dmap_t *dmap = audio_engines[portc->audio_dev]->dmap_in;

	      if (astatus & (AI_RFF << portc->rec_port))
		portc->rec_ptr = 0;
	      if (astatus & (AI_RFH << portc->rec_port))
		portc->rec_ptr = dmap->bytes_in_use / 2;

	      oss_audio_inputintr (portc->audio_dev, 0);
	    }
	}
      write_reg (devc, AINT_STATUS, 0, astatus);
    }

  if (status & INTR_IT1)
    {
      for (i = 0; i < MAX_PORTC; i++)
	{
	  portc = &devc->portc[i];

	  if ((portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    check_playback_intr (devc, portc);

	  if ((portc->trigger_bits & PCM_ENABLE_INPUT))
	    check_recording_intr (devc, portc);
	}
    }

  serviced = 1;
  OUTL (devc->osdev, status, devc->base + 0x08);	/* Acknowledge */
  MUTEX_EXIT (devc->mutex, flags);
  return serviced;
}

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

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

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

  if (arg != 44100 && arg != 48000 && arg != 96000 && arg != 192000)
    arg = 48000;

  portc->speed = arg;
  return portc->speed;
}

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

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

  if ((arg == 1))
    arg = 2;

  if (portc->open_mode & OPEN_READ)
    return portc->channels = 2;

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

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

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

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

  return portc->bits = arg;
}

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

static void audigyls_trigger (int dev, int state);

static void
audigyls_reset (int dev)
{
  audigyls_trigger (dev, 0);
}

/*ARGSUSED*/
static int
audigyls_open (int dev, int mode, int open_flags)
{
  audigyls_portc *portc = audio_engines[dev]->portc;
  audigyls_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;
    }
  portc->open_mode = mode;
  portc->audio_enabled = ~mode;
  portc->play_ptr = portc->rec_ptr = 0;

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

static void
audigyls_close (int dev, int mode)
{
  audigyls_portc *portc = audio_engines[dev]->portc;

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

/*ARGSUSED*/
static void
audigyls_output_block (int dev, oss_native_word buf, int count, int fragsize,
		       int intrflag)
{
}

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

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

#ifdef USE_ITIMER
static void
check_itimer (audigyls_devc * devc)
{
  int i;
  unsigned int t = 0x1fffffff;
  audigyls_portc *portc;
  int tmp;

  for (i = 0; i < MAX_PORTC; i++)
    {
      portc = &devc->portc[i];
      if (portc->frag_192khz != 0 && portc->frag_192khz < t)
	t = portc->frag_192khz;
    }
  if (t == 0x1fffffff)		/* No audio devices active */
    {
      tmp = INL (devc->osdev, devc->base + IE);
      tmp &= ~INTR_IT1;
      OUTL (devc->osdev, tmp, devc->base + IE);
    }
  else
    {
      t /= 16;
      if (t < 1)
	t = 1;
      write_reg (devc, IT, 0, t);
      tmp = INL (devc->osdev, devc->base + IE);
      tmp |= INTR_IT1;
      OUTL (devc->osdev, tmp, devc->base + IE);
    }
}

static void
adjust_itimer (audigyls_devc * devc, audigyls_portc * portc, dmap_p dmap)
{
  unsigned int t;

  /* Compute byte rate */

  t = portc->speed * portc->channels;

  switch (portc->bits)
    {
    case AFMT_S16_LE:
    case AFMT_S16_BE:
    case AFMT_AC3:
      t *= 2;
      break;

    case AFMT_S32_LE:
    case AFMT_S32_BE:
    case AFMT_S24_LE:
    case AFMT_S24_BE:
      t *= 4;
      break;
    }

/* Compute the number of 192kHz ticks per fragment */

  t = (dmap->fragment_size * 192000) / t;	/* msecs / fragment */
  if (t < 1)
    t = 1;

  portc->frag_192khz = t;
  check_itimer (devc);
}
#endif

static void
audigyls_trigger (int dev, int state)
{
  audigyls_devc *devc = audio_engines[dev]->devc;
  audigyls_portc *portc = audio_engines[dev]->portc;
  int tmp;
  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))
	    {
	      tmp = read_reg (devc, SA, 0);
	      tmp |= 1 << portc->play_port;
	      write_reg (devc, SA, 0, tmp);
#ifdef USE_ITIMER
	      check_itimer (devc);
#else
	      tmp = read_reg (devc, AINT_ENABLE, 0);
	      tmp |= ((AI_PFH | AI_PFF) << portc->play_port);
	      write_reg (devc, AINT_ENABLE, 0, tmp);
#endif
	      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;
#ifdef USE_ITIMER
	      portc->frag_192khz = 0;
	      check_itimer (devc);
#endif
	      /* Disable Play channel */
	      tmp = read_reg (devc, SA, 0);
	      tmp &= ~(1 << portc->play_port);
	      write_reg (devc, SA, 0, tmp);
#ifndef USE_ITIMER
	      tmp = read_reg (devc, AINT_ENABLE, 0);
	      tmp &= ~((AI_PFH | AI_PFF) << portc->play_port);
	      write_reg (devc, AINT_ENABLE, 0, tmp);
#endif
	    }
	}
    }

  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      /* Enable Rec Channel */
	      tmp = read_reg (devc, SA, 0);
	      tmp |= 0x100 << portc->rec_port;	/* enable record */
	      write_reg (devc, SA, 0, tmp);
#ifdef USE_ITIMER
	      check_itimer (devc);
#else
	      tmp = read_reg (devc, AINT_ENABLE, 0);
	      tmp |= ((AI_RFF | AI_RFH) << portc->rec_port);
	      write_reg (devc, AINT_ENABLE, 0, tmp);
#endif
	      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;
#ifdef USE_ITIMER
	      portc->frag_192khz = 0;
	      check_itimer (devc);
#endif
	      /* disable channel */
	      tmp = read_reg (devc, SA, 0);
	      tmp &= ~(0x100 << portc->rec_port);
	      write_reg (devc, SA, 0, tmp);
#ifndef USE_ITIMER
	      tmp = read_reg (devc, AINT_ENABLE, 0);
	      tmp &= ~((AI_RFF | AI_RFH) << portc->rec_port);
	      write_reg (devc, AINT_ENABLE, 0, tmp);
#endif
	    }
	}
    }

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

/*ARGSUSED*/
static int
audigyls_prepare_for_input (int dev, int bsize, int bcount)
{
  unsigned int tmp, recmap, reg;
   /*LINTED*/ unsigned int oversample;
  audigyls_devc *devc = audio_engines[dev]->devc;
  audigyls_portc *portc = audio_engines[dev]->portc;
  dmap_p dmap = audio_engines[dev]->dmap_in;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  write_reg (devc, CRFA, portc->rec_port, 0);
  write_reg (devc, CRCAV, portc->rec_port, 0);

  write_reg (devc, RFBA, portc->rec_port, dmap->dmabuf_phys);
  write_reg (devc, RFBS, portc->rec_port, (dmap->bytes_in_use) << 16);

  /* set 16/24 bits */
  tmp = INL (devc->osdev, devc->base + 0x14);
  if (portc->bits == AFMT_S16_LE)
    tmp &= ~0x400;		/*16 bit */
  else
    tmp |= 0x400;		/*24 bit */
  OUTL (devc->osdev, tmp, devc->base + 0x14);

  /* set recording speed */
  reg = read_reg (devc, SRCTL, 0) & ~0xc000;

  switch (portc->speed)
    {
    case 48000:
      reg |= 0x0;
      oversample = 0x2;
      break;

    case 96000:
      reg |= 0x8000;
      oversample = 0xa;
      break;

    case 192000:
      reg |= 0xc000;
      oversample = 0xa;
      break;

    default:
      reg |= 0;
      oversample = 0x2;
      break;
    }

  write_reg (devc, SRCTL, 0, reg);
/*  audigyls_i2c_write(devc, 0xc, oversample);*/

/* setup record input */
  if (devc->loopback)
    {
      devc->rec_src = RECSEL_I2SOUT;
      recmap = 0;
    }
  else
    {
      if (devc->has_ac97)
	{
	  devc->rec_src = RECSEL_AC97;	/* audigy LS */
	}
      else
	{
	  devc->rec_src = RECSEL_I2SIN;	/* sb 7.1 value */
	}
      recmap = 0x00; 
    }
  tmp = recmap;			/* default record input map */
  tmp |= devc->rec_src << 28 | devc->rec_src << 24 | devc->rec_src << 20 | devc->rec_src << 16; 
//write_reg (devc, SMIXMAP_SPDIF, 0, 0x76767676); 
  write_reg (devc, P17RECSEL, 0, tmp);
  portc->audio_enabled &= ~PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;

#ifdef USE_ITIMER
  adjust_itimer (devc, portc, dmap);
#endif

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

/*ARGSUSED*/
static int
audigyls_prepare_for_output (int dev, int bsize, int bcount)
{
  audigyls_devc *devc = audio_engines[dev]->devc;
  audigyls_portc *portc = audio_engines[dev]->portc;
  dmap_p dmap = audio_engines[dev]->dmap_out;
  unsigned int tmp, reg;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  if (portc->bits == AFMT_AC3)
    portc->channels = 2;

  write_reg (devc, PTBA, portc->play_port, 0);
  write_reg (devc, PTBS, portc->play_port, 0);
  write_reg (devc, PTCA, portc->play_port, 0);

  write_reg (devc, CPFA, portc->play_port, 0);
  write_reg (devc, PFEA, portc->play_port, 0);
  write_reg (devc, CPCAV, portc->play_port, 0);


  /* set 16/24 bits */
  tmp = INL (devc->osdev, devc->base + 0x14);
  if (portc->bits == AFMT_S16_LE)
    tmp &= ~0x800;		/*16 bit */
  else
    tmp |= 0x800;		/*24 bit */
  OUTL (devc->osdev, tmp, devc->base + 0x14);

  /* set playback rate */
  tmp = read_reg (devc, SA, 0) & ~0xff0000;
  reg = read_reg (devc, SRCTL, 0) & ~0x0303000f;
#if 0
  switch (portc->speed)
    {
    case 48000:
      tmp |= 0;
      reg |= 0;
      break;

    case 44100:
      tmp |= 0x10000;
      reg |= 0x01010005;
      break;

    case 96000:
      tmp |= 0x20000;
      reg |= 0x0202000a;
      break;

    case 192000:
      tmp |= 0x30000;
      reg |= 0x0303000f;
      break;

    default:
      tmp |= 0;			/* default is 48000 */
      reg |= 0;
      break;
    }
#endif
  write_reg (devc, SA, 0, tmp << (portc->play_port * 2));
  write_reg (devc, SRCTL, 0, reg);

  /* Single buffering mode */
  write_reg (devc, PFBA, portc->play_port, dmap->dmabuf_phys);
  write_reg (devc, PFBS, portc->play_port, (dmap->bytes_in_use) << 16);

  if (audigyls_spdif_enable)
    {
      if (portc->bits == AFMT_AC3)
	{
	  audigyls_ac97_write (devc, 0x1c, 0x8000);
	  write_reg (devc, SCS3, 0, 0x02108006);	/* Non Audio */
#if 0
	  write_reg (devc, SCS0, 0, 0x02108006);	/* Non Audio */
	  write_reg (devc, SCS1, 0, 0x02108006);	/* Non Audio */
	  write_reg (devc, SCS2, 0, 0x02108006);	/* Non Audio */
#endif
	}
      else
	{
	  write_reg (devc, SCS3, 0, 0x02108004);	/* Audio */
#if 0
	  write_reg (devc, SCS0, 0, 0x02108004);	/* Audio */
	  write_reg (devc, SCS1, 0, 0x02108004);	/* Audio */
	  write_reg (devc, SCS2, 0, 0x02108004);	/* Audio */
#endif
	}
    }
  portc->audio_enabled |= PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;

#ifdef USE_ITIMER
  adjust_itimer (devc, portc, dmap);
#endif

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

static int
audigyls_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  unsigned int p = 0;
  audigyls_portc *portc = audio_engines[dev]->portc;

#if 1
  audigyls_devc *devc = audio_engines[dev]->devc;

  if (direction == PCM_ENABLE_OUTPUT)
    {
      p = read_reg (devc, CPFA, portc->play_port);
    }

  if (direction == PCM_ENABLE_INPUT)
    {
      p = read_reg (devc, CRFA, portc->rec_port);
    }


Round to the nearest fragment boundary.

  p = (p + dmap->fragment_size / 2);
  p = (p / dmap->fragment_size) * dmap->fragment_size;
#else
  if (direction == PCM_ENABLE_OUTPUT)
    {
      return portc->play_ptr;
    }

  if (direction == PCM_ENABLE_INPUT)
    {
      return portc->rec_ptr;
    }
#endif

  return p % dmap->bytes_in_use;
}

static audiodrv_t audigyls_audio_driver = {
  audigyls_open,
  audigyls_close,
  audigyls_output_block,
  audigyls_start_input,
  audigyls_ioctl,
  audigyls_prepare_for_input,
  audigyls_prepare_for_output,
  audigyls_reset,
  NULL,
  NULL,
  NULL,
  NULL,
  audigyls_trigger,
  audigyls_set_rate,
  audigyls_set_format,
  audigyls_set_channels,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,				/* audigyls_alloc_buffer, */
  NULL,				/* audigyls_free_buffer */
  NULL,
  NULL,
  audigyls_get_buffer_pointer
};


static __inline__ int
audigylsuart_status (audigyls_devc * devc)
{
  return read_reg (devc, MUCMDA, 0);
}

#define input_avail(devc) (!(audigylsuart_status(devc)&INPUT_AVAIL))
#define output_ready(devc)      (!(audigylsuart_status(devc)&OUTPUT_READY))
static void
audigylsuart_cmd (audigyls_devc * devc, unsigned char cmd)
{
  write_reg (devc, MUCMDA, 0, cmd);
}

static __inline__ int
audigylsuart_read (audigyls_devc * devc)
{
  return read_reg (devc, MUDATA, 0);
}

static __inline__ void
audigylsuart_write (audigyls_devc * devc, unsigned char byte)
{
  write_reg (devc, MUDATA, 0, byte);
}

#define OUTPUT_READY    0x40
#define INPUT_AVAIL     0x80
#define MPU_ACK         0xFE
#define MPU_RESET       0xFF
#define UART_MODE_ON    0x3F

static int reset_audigylsuart (audigyls_devc * devc);
static void enter_uart_mode (audigyls_devc * devc);

static void
audigylsuart_input_loop (audigyls_devc * devc)
{
  while (input_avail (devc))
    {
      unsigned char c = audigylsuart_read (devc);

      if (c == MPU_ACK)
	devc->input_byte = c;
      else if (devc->midi_opened & OPEN_READ && devc->midi_input_intr)
	devc->midi_input_intr (devc->midi_dev, c);
    }
}

static void
audigylsuartintr (audigyls_devc * devc)
{
  audigylsuart_input_loop (devc);
}

/*ARGSUSED*/
static int
audigylsuart_open (int dev, int mode, oss_midi_inputbyte_t inputbyte,
		   oss_midi_inputbuf_t inputbuf,
		   oss_midi_outputintr_t outputintr)
{
  audigyls_devc *devc = (audigyls_devc *) midi_devs[dev]->devc;

  if (devc->midi_opened)
    {
      return OSS_EBUSY;
    }

  while (input_avail (devc))
    audigylsuart_read (devc);

  devc->midi_input_intr = inputbyte;
  devc->midi_opened = mode;
  enter_uart_mode (devc);
  devc->midi_disabled = 0;

  return 0;
}

/*ARGSUSED*/
static void
audigylsuart_close (int dev, int mode)
{
  audigyls_devc *devc = (audigyls_devc *) midi_devs[dev]->devc;
  reset_audigylsuart (devc);
  oss_udelay (10);
  enter_uart_mode (devc);
  reset_audigylsuart (devc);
  devc->midi_opened = 0;
}


static int
audigylsuart_out (int dev, unsigned char midi_byte)
{
  int timeout;
  audigyls_devc *devc = (audigyls_devc *) midi_devs[dev]->devc;
  oss_native_word flags;


Test for input since pending input seems to block the output.


  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (input_avail (devc))
    audigylsuart_input_loop (devc);

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);


Sometimes it takes about 130000 loops before the output becomes ready (After reset). Normally it takes just about 10 loops.


  for (timeout = 130000; timeout > 0 && !output_ready (devc); timeout--);

  if (!output_ready (devc))
    {
      cmn_err (CE_WARN, "UART timeout - Device not responding\n");
      devc->midi_disabled = 1;
      reset_audigylsuart (devc);
      enter_uart_mode (devc);
      return 1;
    }

  audigylsuart_write (devc, midi_byte);
  return 1;
}

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

static midi_driver_t audigyls_midi_driver = {
  audigylsuart_open,
  audigylsuart_close,
  audigylsuart_ioctl,
  audigylsuart_out,
};

static void
enter_uart_mode (audigyls_devc * devc)
{
  int ok, timeout;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  for (timeout = 30000; timeout > 0 && !output_ready (devc); timeout--);

  devc->input_byte = 0;
  audigylsuart_cmd (devc, UART_MODE_ON);

  ok = 0;
  for (timeout = 50000; timeout > 0 && !ok; timeout--)
    if (devc->input_byte == MPU_ACK)
      ok = 1;
    else if (input_avail (devc))
      if (audigylsuart_read (devc) == MPU_ACK)
	ok = 1;

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}


void
attach_audigylsuart (audigyls_devc * devc)
{
  enter_uart_mode (devc);
  devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "AUDIGYLS", "AudigyLS UART", &audigyls_midi_driver, sizeof (midi_driver_t),
					0, devc, devc->osdev);
  devc->midi_opened = 0;
}

static int
reset_audigylsuart (audigyls_devc * devc)
{
  int ok, timeout, n;


Send the RESET command. Try again if no success at the first time.


  ok = 0;

  for (n = 0; n < 2 && !ok; n++)
    {
      for (timeout = 30000; timeout > 0 && !output_ready (devc); timeout--);

      devc->input_byte = 0;
      audigylsuart_cmd (devc, MPU_RESET);


Wait at least 25 msec. This method is not accurate so let's make the loop bit longer. Cannot sleep since this is called during boot.


      for (timeout = 50000; timeout > 0 && !ok; timeout--)
	if (devc->input_byte == MPU_ACK)	/* Interrupt */
	  ok = 1;
	else if (input_avail (devc))
	  if (audigylsuart_read (devc) == MPU_ACK)
	    ok = 1;

    }



  if (ok)

Flush input before enabling interrupts


  return ok;
}


int
probe_audigylsuart (audigyls_devc * devc)
{
  int ok = 0;
  oss_native_word flags;

  DDB (cmn_err (CE_CONT, "Entered probe_audigylsuart\n"));

  devc->midi_input_intr = NULL;
  devc->midi_opened = 0;
  devc->input_byte = 0;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  ok = reset_audigylsuart (devc);
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  if (ok)
    {
      DDB (cmn_err (CE_CONT, "Reset UART401 OK\n"));
    }
  else
    {
      DDB (cmn_err
	   (CE_CONT, "Reset UART401 failed (no hardware present?).\n"));
      DDB (cmn_err
	   (CE_CONT, "mpu401 status %02x\n", audigylsuart_status (devc)));
    }

  DDB (cmn_err (CE_CONT, "audigylsuart detected OK\n"));
  return ok;
}

void
unload_audigylsuart (audigyls_devc * devc)
{
  reset_audigylsuart (devc);
}


static void
attach_mpu (audigyls_devc * devc)
{
  devc->mpu_attached = 1;
  attach_audigylsuart (devc);
}

/* only for SBLive 7.1 */
int
audigyls_i2c_write (audigyls_devc * devc, int reg, int data)
{
  int i, timeout, tmp;


  tmp = (reg << 9 | data) << 16;	/* set the upper 16 bits */
  /* first write the command to the data reg */
  write_reg (devc, I2C_1, 0, tmp);
  for (i = 0; i < 20; i++)
    {
      tmp = read_reg (devc, I2C_A, 0) & ~0x6fe;
      /* see audigyls.pdf for bits */
      tmp |= 0x400 | 0x100 | 0x34;
      write_reg (devc, I2C_A, 0, tmp);
      /* now wait till controller sets valid bit (0x100) to 0 */
      timeout = 0;
       /*LINTED*/ while (1)
	{
	  tmp = read_reg (devc, I2C_A, 0);
	  if ((tmp & 0x100) == 0)
	    break;

	  if (timeout > 100)
	    break;

	  timeout++;
	}

      /* transaction aborted */
      if (tmp & 0x200)
	return 0;
    }
  return 1;
}

int
audigyls_spi_write (audigyls_devc * devc, int data)
{
  unsigned int orig;
  unsigned int tmp;
  int i, valid;

  tmp = read_reg (devc, SPI, 0);
  orig = (tmp & ~0x3ffff) | 0x30000;
  write_reg (devc, SPI, 0, orig | data);
  valid = 0;
  /* Wait for status bit to return to 0 */
  for (i = 0; i < 1000; i++)
    {
      oss_udelay (100);
      tmp = read_reg (devc, SPI, 0);
      if (!(tmp & 0x10000))
	{
	  valid = 1;
	  break;
	}
    }
  if (!valid)			/* Timed out */
    return 0;

  return 1;
}

static unsigned int
mix_scale (int left, int right, int bits)
{
  left = mix_cvt[left];
  right = mix_cvt[right];

  return ((left * ((1 << bits) - 1) / 100) << 8) | (right *
						    ((1 << bits) - 1) / 100);
}

static int
audigyls_set_volume (audigyls_devc * devc, int codecid, int value)
{
  audigyls_portc *portc = NULL;
  int left, right, i2s_vol;

  portc = &devc->portc[codecid];

  left = value & 0xff;
  right = (value >> 8) & 0xff;
  if (left > 100)
    left = 100;
  if (right > 100)
    right = 100;
  devc->playvol[codecid] = left | (right << 8);

  i2s_vol = 65535 - mix_scale (left, right, 8);
  write_reg (devc, MIXVOL_I2S, portc->play_port, (i2s_vol << 16));
  return devc->playvol[codecid];
}

int
audigyls_mix_control (int dev, int ctrl, unsigned int cmd, int value)
{
  audigyls_devc *devc = mixer_devs[dev]->hw_devc;
  int val;

  if (cmd == SNDCTL_MIX_READ)
    {
      value = 0;
      switch (ctrl)
	{
	case 1:		/* spread */
	  value = devc->spread;
	  break;

	case 2:		/* record what you hear */
	  value = devc->loopback;
	  break;

	case 3:
	  {
	    value = devc->recvol;
	  }
	  break;

	case 4: 
	  value = devc->input_source;
	  break;
	case 5: 
	  value = devc->captmon; 
	  break; 
	case 7: 
	  value = devc->fbvol; 
	  break; 
	}
    }
  if (cmd == SNDCTL_MIX_WRITE)
    {
      switch (ctrl)
	{
	case 1:		/* recording source */
	  devc->spread = value;
	  if (value)
	    write_reg (devc, HMIXMAP_I2S, 0, 0x10101010);
	  else
	    write_reg (devc, HMIXMAP_I2S, 0, 0x76543210);
	  break;

	case 2:		/* record what you hear */
	  devc->loopback = value;
	  break;

	case 3:
	  {
	    val = (255 - value) & 0xff;
	    write_reg (devc, P17RECVOLL, 0,
		       val << 24 | val << 16 | val << 8 | val);
	    write_reg (devc, P17RECVOLH, 0,
		       val << 24 | val << 16 | val << 8 | val);

0xff << 24 | 0xff << 16 | val << 8 | val);

	    devc->recvol = value & 0xff;
	  }
	  break;

	case 4:
	  { 
	  switch (value) 
	    {
          case 0:   /* for mic input remove GPIO */ 
	    {
	      OUTL (devc->osdev, INL (devc->osdev, devc->base + 0x18) | 0x400,
		    devc->base + 0x18);
	      audigyls_i2c_write (devc, 0x15, 0x2);	/* Mic */
	    }
            break; 
          case 1: 
	    {
	      OUTL (devc->osdev,
		    INL (devc->osdev, devc->base + 0x18) & ~0x400,
		    devc->base + 0x18);
	      audigyls_i2c_write (devc, 0x15, 0x4);	/* Line */
	    }
            break; 
          case 2: 
            { 
              OUTL (devc->osdev, 
              INL (devc->osdev, devc->base + 0x18) & ~0x400, 
              devc->base + 0x18); 
              audigyls_i2c_write (devc, 0x15, 0x8);   /* Aux */ 
            } 
          break; 
	    } 
	  devc->input_source = value;
	} 
	break; 
	case 5: 
	  { 
	    devc->captmon = value; 
	    /* Send analog capture to front speakers */ 
	    if (value) 
	      write_reg (devc, SMIXMAP_I2S, 0, 0x76767676); 
	    else 
	     write_reg (devc, SMIXMAP_I2S, 0, 0x10101010); 
	  } 
	break; 
	case 7: 
	  { 
	    /*Set recording monitor volume */ 
	    val = (255 - value) & 0xff; 
	    write_reg (devc, SRCTL, 1, val << 8 | val); 
	    devc->fbvol = value & 0xff; 
	  } 
	break;
	}
    }
  return value;
}

static int
audigyls_mix_init (int dev)
{
  int group, err;
  audigyls_devc *devc = mixer_devs[dev]->hw_devc;

  if ((group = mixer_ext_create_group (dev, 0, "EXT")) < 0)
    return group;

  if ((err = mixer_ext_create_control (dev, group, 1, audigyls_mix_control,
				       MIXT_ONOFF, "Spread", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group, 2, audigyls_mix_control,
				       MIXT_ONOFF, "LOOPBACK", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;


  if ((err = mixer_ext_create_control (dev, group, 3, audigyls_mix_control,
				       MIXT_MONOSLIDER, "RECORDVOL", 255,
				       MIXF_READABLE | MIXF_WRITEABLE |
				       MIXF_RECVOL)) < 0)
    return err;

  if (!devc->has_ac97)
    {
      if ((err =
	   mixer_ext_create_control (dev, group, 4, audigyls_mix_control,
				     MIXT_ENUM, "RECORDSRC", 3, 
				     MIXF_READABLE | MIXF_WRITEABLE)) < 0)
	return err;
      mixer_ext_set_strings (dev, err, "MIC LINE AUX", 0); 
    }
  if ((err = mixer_ext_create_control (dev, group, 7, audigyls_mix_control, 
				       MIXT_MONOSLIDER, "monitorvol", 255,
				       MIXF_READABLE | MIXF_WRITEABLE |
				       MIXF_RECVOL)) < 0)
    return err;
  if ((err = mixer_ext_create_control (dev, group, 5, audigyls_mix_control, 
				       MIXT_ONOFF, "RecMon", 1, 
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0) 
    return err; 
  return 0;
}

static const int bindings[MAX_PORTC] = {
  DSP_BIND_FRONT,
  DSP_BIND_CENTER_LFE,
  DSP_BIND_SURR
};

static int
install_audio_devices (audigyls_devc * devc)
{
  int i;
  int frontdev = -1;
  int adev, flags;
  int fmts = AFMT_S16_LE | AFMT_AC3;
  static char *names[] = {
    "AudigyLS front",
    "AudigyLS center/lfe",
    "AudigyLS surround"
  };

#if 0
  if (audigyls_spdif_enable == 1)
    n = 2;
#endif

  for (i = 0; i < MAX_PORTC; i++)
    {
      audigyls_portc *portc = &devc->portc[i];

      flags =
	ADEV_AUTOMODE | ADEV_16BITONLY | ADEV_STEREOONLY | ADEV_FIXEDRATE;

      switch (i)
	{
	case 0:
	  portc->play_port = 0;
	  portc->rec_port = 2;
	  flags |= ADEV_DUPLEX;
	  break;
	case 1:
	  portc->play_port = 1;
	  portc->rec_port = 2;
	  flags |= ADEV_NOINPUT;
	  break;
	case 2:
	  portc->play_port = 3;
	  portc->rec_port = 2;
	  flags |= ADEV_NOINPUT;
	  break;
	}

      if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					devc->osdev,
					devc->osdev,
					names[i],
					&audigyls_audio_driver,
					sizeof (audiodrv_t),
					flags, fmts, devc, -1)) < 0)
	{
	  return 0;
	}

      if (i == 0)
	frontdev = adev;

      audio_engines[adev]->portc = portc;
      audio_engines[adev]->max_fragments = 2;
      audio_engines[adev]->dmabuf_alloc_flags |= DMABUF_SIZE_16BITS;
      audio_engines[adev]->rate_source = frontdev;
      audio_engines[adev]->mixer_dev = devc->mixer_dev;
      audio_engines[adev]->binding = bindings[i];
      if (audio_engines[adev]->flags & ADEV_FIXEDRATE)
	{
	  audio_engines[adev]->fixed_rate = DEFAULT_RATE;
	  audio_engines[adev]->min_rate = DEFAULT_RATE;
	  audio_engines[adev]->max_rate = DEFAULT_RATE;
	}
      else
	{
	  audio_engines[adev]->min_rate = 44100;
	  audio_engines[adev]->max_rate = 192000;
	}
      portc->audio_dev = adev;
      portc->open_mode = 0;
      devc->playvol[i] = 0x3030;
      devc->recvol = 128;
      portc->bits = AFMT_S16_LE;
    }

#ifdef USE_REMUX
  if (frontdev >= 0)
    {
      if (audigyls_spdif_enable && devc->has_ac97)
	remux_install ("AudigyLS 4.0 output", devc->osdev, frontdev,
		       frontdev + 2, -1, -1);
      else
	remux_install ("AudigyLS 5.1 output", devc->osdev, frontdev,
		       frontdev + 2, frontdev + 1, -1);
    }
#endif

#ifdef CONFIG_OSS_VMIX
	  if (frontdev >= 0)
	     vmix_attach_audiodev(devc->osdev, frontdev, -1, 0);
#endif
  return 1;
}

static void
select_out3_mode (audigyls_devc * devc, int mode)
{

Set the out3/spdif combo jack format. mode0=analog rear/center, 1=spdif


  if (mode == 0)
    {
      write_reg (devc, SPC, 0, 0x00000f00);
    }
  else
    {
      write_reg (devc, SPC, 0, 0x0000000f);
    }
}

/*ARGSUSED*/
static int
audigyls_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg)
{
  audigyls_devc *devc = mixer_devs[dev]->devc;

  if (((cmd >> 8) & 0xff) == 'M')
    {
      int val;

      if (IOC_IS_OUTPUT (cmd))
	switch (cmd & 0xff)
	  {
	  case SOUND_MIXER_RECSRC:
	    return *arg = 0;
	    break;

	  case SOUND_MIXER_PCM:
	    val = *arg;
	    return *arg = audigyls_set_volume (devc, 0, val);

	  case SOUND_MIXER_CENTERVOL:
	    val = *arg;
	    return *arg = audigyls_set_volume (devc, 2, val);

	  case SOUND_MIXER_REARVOL:
	    val = *arg;
	    return *arg = audigyls_set_volume (devc, 3, val);
	  }
      else
	switch (cmd & 0xff)	/* Return Parameter */
	  {
	  case SOUND_MIXER_RECSRC:
	  case SOUND_MIXER_RECMASK:
	    return *arg = 0;
	    break;

	  case SOUND_MIXER_DEVMASK:
	    return *arg =
	      SOUND_MASK_PCM | SOUND_MASK_REARVOL | SOUND_MASK_CENTERVOL;
	    break;

	  case SOUND_MIXER_STEREODEVS:
	    return *arg =
	      SOUND_MASK_PCM | SOUND_MASK_REARVOL | SOUND_MASK_CENTERVOL;
	    break;

	  case SOUND_MIXER_CAPS:
	    return *arg = SOUND_CAP_EXCL_INPUT;
	    break;

	  case SOUND_MIXER_PCM:
	    return *arg = devc->playvol[0];
	    break;

	  case SOUND_MIXER_CENTERVOL:
	    return *arg = devc->playvol[2];
	    break;

	  case SOUND_MIXER_REARVOL:
	    return *arg = devc->playvol[3];
	    break;
	  }
    }
  else
    return *arg = 0;

  return OSS_EINVAL;
}

static mixer_driver_t audigyls_mixer_driver = {
  audigyls_mixer_ioctl
};

int
oss_audigyls_attach (oss_device_t * osdev)
{
  int tmp, err, i, tries;
  unsigned char pci_irq_line, pci_revision;
  unsigned short pci_command, vendor, device;
  unsigned int pci_ioaddr;
  unsigned int subvendor;
  audigyls_devc *devc;

  static unsigned int spi_dac[] = {
    0x00ff, 0x02ff, 0x0400, 0x530, 0x0622, 0x08ff, 0x0aff, 0x0cff,
    0x0eff, 0x10ff, 0x1200, 0x1400, 0x1800, 0x1aff, 0x1cff,
    0x1e00,
  };

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

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

  if (vendor != PCI_VENDOR_ID_CREATIVE ||
      device != PCI_DEVICE_ID_CREATIVE_AUDIGYLS)
    return 0;

  pci_read_config_dword (osdev, 0x2c, &subvendor);
  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_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.\n");
      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;
  devc->card_name = "AudigyLS";
  devc->subvendor = subvendor;

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

  devc->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr);
  devc->base &= ~0x3;

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

  oss_register_device (osdev, devc->card_name);

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



Init mixer

  if (subvendor == 0x10021102)	/* original audigyls */
    {
      devc->mixer_dev = ac97_install (&devc->ac97devc, devc->card_name,
				      audigyls_ac97_read, audigyls_ac97_write,
				      devc, devc->osdev);
      devc->has_ac97 = 1;
      audigyls_ac97_write (devc, 0x1c, 0x8000);
    }
  else
    {
      devc->mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION,
					   devc->osdev,
					   devc->osdev,
					   "AudigyLS Mixer",
					   &audigyls_mixer_driver,
					   sizeof (mixer_driver_t), devc);
      devc->has_ac97 = 0;	/* no ac97 */
      mixer_devs[devc->mixer_dev]->hw_devc = devc;
      mixer_devs[devc->mixer_dev]->priority = 1;	/* Possible default mixer candidate */
    }

  mixer_ext_set_init_fn (devc->mixer_dev, audigyls_mix_init, 10);

#if 0
  write_reg (devc, SCS0, 0, 0x02108504);
  write_reg (devc, SCS1, 0, 0x02108504);
  write_reg (devc, SCS2, 0, 0x02108504);
#endif
  write_reg (devc, SCS3, 0, 0x02108504);

  write_reg (devc, AUDCTL, 0, 0x0f0f003f);	/* enable all outputs */

  select_out3_mode (devc, audigyls_spdif_enable);


In P17, there's 8 GPIO pins. GPIO register: 0x00XXYYZZ XX: Configure GPIO to be either GPI (0) or GPO (1). YY: GPO values, applicable if the pin is configure to be GPO. ZZ: GPI values, applicable if the pin is configure to be GPI.

in SB570, pin 0-4 and 6 is used as GPO and pin 5 and 7 is used as GPI.

GPO0: 1 ==> Analog output 0 ==> Digital output GPO1: 1 ==> Enable output on card 0 ==> Diable output on card GPO2: 1 ==> Enable Mic Bias and Mic Path 0 ==> Disable Mic Bias and Mic Path GPO3: 1 ==> Disable SPDIF-IO output 0 ==> Enable SPDIF-IO output GPO4 and GPO6: DAC sampling rate selection: Not applicable to SB570 since DAC is controlled through SPI GPI5: 1 ==> Front Panel is not connected 0 ==> Front Panel is connected GPI7: 1 ==> Front Panel Headphone is not connected 0 ==> Front Panel Headphone is connected



  OUTL (devc->osdev, 0, devc->base + 0x18);	/* GPIO */
  if (devc->has_ac97)
    OUTL (devc->osdev, 0x005f03a3, devc->base + 0x18);
  else
    {
      /* for SBLive 7.1 */
      OUTL (devc->osdev, 0x005f4301, devc->base + 0x18);
      audigyls_i2c_write (devc, 0x15, 0x4);
      tries = 0;
    again:
      for (i = 0; i < sizeof (spi_dac); i++)
	{
	  if (!audigyls_spi_write (devc, spi_dac[i]) && tries < 100)
	    {
	      tries++;
	      goto again;
	    }
	}
    }

  OUTL (devc->osdev, INTR_PCI | INTR_RXA | INTR_AI, devc->base + IE);
  OUTL (devc->osdev, 0x00000009, devc->base + 0x14);	/* Enable audio */

  tmp = read_reg (devc, SRCTL, 0);
  if (devc->has_ac97)
    tmp |= 0xf0c81000;		/* record src0/src1 from ac97 */
  else
    tmp |= 0x50c81000;		/* record src0/src1 from I2SIN */
  write_reg (devc, SRCTL, 0, tmp);
  write_reg (devc, HMIXMAP_I2S, 0, 0x76543210);	/* default out route */
  install_audio_devices (devc);

  if (devc->has_ac97)		/* only attach midi for AudigyLS */
    attach_mpu (devc);

  return 1;
}

int
oss_audigyls_detach (oss_device_t * osdev)
{
  unsigned int status;
  audigyls_devc *devc = (audigyls_devc *) osdev->devc;

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


  write_reg (devc, SA, 0, 0);
  OUTL (devc->osdev, 0x00000000, devc->base + IE);	/* Interrupt disable */
  write_reg (devc, AINT_ENABLE, 0, 0);	/* Disable audio interrupts */
  status = INL (devc->osdev, devc->base + 0x08);
  OUTL (devc->osdev, status, devc->base + 0x08);	/* Acknowledge */
  oss_udelay (1000);
  if (devc->mpu_attached)
    unload_audigylsuart (devc);

  oss_unregister_interrupts (devc->osdev);

  MUTEX_CLEANUP (devc->mutex);
  MUTEX_CLEANUP (devc->low_mutex);
  UNMAP_PCI_IOADDR (devc->osdev, 0);

  oss_unregister_device (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