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_audiopci/oss_audiopci.c

Creative/Ensoniq AudioPCI driver (ES1370 "CONCERT" ASIC and AKM4531 codec/mixer)

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_audiopci_cfg.h"
#include "audiopci.h"
#include "midi_core.h"
#include "oss_pci.h"

#define ENSONIQ_VENDOR_ID	0x1274
#define ENSONIQ_VENDOR_ID2	0x1275
#define ENSONIQ_AUDIOPCI	0x5000

#define MAX_PORTC 2

typedef struct apci_portc
{

  /* Audio parameters */
  int audiodev;
  int open_mode;
  int trigger_bits;
  int audio_enabled;
  int speed, bits, channels;
  int atype;			/* 0=DAC/ADC, 1=Synth */
  int speedsel;
} apci_portc;

typedef struct apci_devc
{
  oss_device_t *osdev;
  oss_mutex_t mutex, low_mutex;
  oss_native_word base;
  int irq;
  char *chip_name;

  /* Mixer parameters */
  int *levels;
  unsigned char ak_regs[0x19];	/* Current mixer register values */
  int recdevs;
  int micbias, micboost;
  unsigned char outsw1, outsw2;

  /* Audio parameters */
  int irq_allocated;
  apci_portc portc[MAX_PORTC];


MIDI

  int midi_opened;
  int midi_dev;
  oss_midi_inputbyte_t midi_input_intr;
} apci_devc;



Initial values to be written into the mixer registers of AK4531 codec.

static const unsigned char ak_reg_init[0x20] = {
  /* Mute all inputs/outputs initially */
  0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,	/* 00 to 07 */
  0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,	/* 08 to 0f */
  0x7f, 0x3d, 0x55, 0x26, 0xf7, 0xef, 0x03, 0x00,	/* 10 to 17 */
  0x00, 0x01			/* 18 to 19 */
};

static void
ak_write (apci_devc * devc, int reg, int value)
{
  int i;

  if (reg < 0 || reg > 0x19)
    return;

  value &= 0xff;
  devc->ak_regs[reg] = (unsigned char) value;

  /* Wait until the codec is ready */
  for (i = 0; i < 0x40000; i++)
    if (!(INB (devc->osdev, devc->base + CONC_bCODECSTAT_OFF) & 0x01))
      break;
  oss_udelay (10);
  OUTW (devc->osdev, (reg << 8) | value, devc->base + CONC_wCODECCTL_OFF);
  oss_udelay (10);
}

static void
apci_writemem (apci_devc * devc, int page, int offs, int data)
{
  int tmp;

  tmp = INL (devc->osdev, devc->base + 0xc);
  OUTL (devc->osdev, page, devc->base + 0xc);	/* Select memory page */
  OUTL (devc->osdev, data, devc->base + offs);
  OUTL (devc->osdev, tmp, devc->base + 0xc);	/* Select the original memory page */
}

static unsigned int
apci_readmem (apci_devc * devc, int page, int offs)
{
  unsigned int val;

  OUTL (devc->osdev, page, devc->base + 0xc);	/* Select memory page */
  val = INL (devc->osdev, devc->base + offs);
  return val;
}

#define bmast_off(x)
#define bmast_on(x)

static int
apci_set_recmask (apci_devc * devc, int mask)
{
  unsigned char tmp;

  mask &= REC_DEVS;


Set lch input mixer SW 1 register

  tmp = 0;
  if (mask & SOUND_MASK_ALTPCM)
    tmp |= 0x40;
  if (mask & SOUND_MASK_LINE)
    tmp |= 0x10;
  if (mask & SOUND_MASK_CD)
    tmp |= 0x04;
  if (mask & SOUND_MASK_MIC)
    tmp |= 0x01;
  ak_write (devc, 0x12, tmp);


Set rch input mixer SW 1 register

  tmp = 0;
  if (mask & SOUND_MASK_ALTPCM)
    tmp |= 0x20;
  if (mask & SOUND_MASK_LINE)
    tmp |= 0x08;
  if (mask & SOUND_MASK_CD)
    tmp |= 0x02;
  if (mask & SOUND_MASK_MIC)
    tmp |= 0x01;
  ak_write (devc, 0x13, tmp);


Set lch input mixer SW 2 register

  tmp = 0;
  if (mask & SOUND_MASK_LINE2)
    tmp |= 0x40;
  if (mask & SOUND_MASK_LINE3)
    tmp |= 0x20;
  if (mask & SOUND_MASK_LINE1)
    tmp |= 0x10;
  if (mask & SOUND_MASK_MIC)
    tmp |= 0x80;
  ak_write (devc, 0x14, tmp);


Set rch input mixer SW 2 register

  tmp = 0;
  if (mask & SOUND_MASK_LINE2)
    tmp |= 0x40;
  if (mask & SOUND_MASK_LINE3)
    tmp |= 0x20;
  if (mask & SOUND_MASK_LINE1)
    tmp |= 0x08;
  if (mask & SOUND_MASK_MIC)
    tmp |= 0x80;
  ak_write (devc, 0x15, tmp);

  return devc->recdevs = mask;
}

/*ARGSUSED*/
static void
change_bits (apci_devc * devc, unsigned char *regval, int dev, int chn,
	     int newval)
{
  unsigned char mask;
  int shift;
  int mute;
  int mutemask;
  int set_mute_bit;

  set_mute_bit = (newval == 0);

  if (ak_mix_devices[dev][chn].polarity == 1)	/* Reverse */
    newval = 100 - newval;

  mask = (1 << ak_mix_devices[dev][chn].nbits) - 1;
  shift = ak_mix_devices[dev][chn].bitpos;

#if 0
  newval = (int) ((newval * mask) + 50) / 100;	/* Scale it */
  *regval &= ~(mask << shift);	/* Clear bits */
  *regval |= (newval & mask) << shift;	/* Set new value */
#else
  if (ak_mix_devices[dev][chn].mutepos == 8)
    {				/* if there is no mute bit */
      mute = 0;			/* No mute bit; do nothing special */
      mutemask = ~0;		/* No mute bit; do nothing special */
    }
  else
    {
      mute = (set_mute_bit << ak_mix_devices[dev][chn].mutepos);
      mutemask = ~(1 << ak_mix_devices[dev][chn].mutepos);
    }

  newval = (int) ((newval * mask) + 50) / 100;	/* Scale it */
  *regval &= (~(mask << shift)) & (mutemask);	/* Clear bits */
  *regval |= ((newval & mask) << shift) | mute;	/* Set new value */
#endif
}

static int
apci_mixer_get (apci_devc * devc, int dev)
{
  if (!((1 << dev) & MIXER_DEVS))
    return OSS_EINVAL;

  return devc->levels[dev];
}

static int
apci_mixer_set (apci_devc * devc, int dev, int value)
{
  int left = value & 0x000000ff;
  int right = (value & 0x0000ff00) >> 8;
  int retvol;

  int regoffs;
  unsigned char val;

  if (dev > 31)
    return OSS_EINVAL;

  if (!(MIXER_DEVS & (1 << dev)))
    return OSS_EINVAL;

  if (left > 100)
    left = 100;
  if (right > 100)
    right = 100;

  if (ak_mix_devices[dev][RIGHT_CHN].regno == 0xff)	/* Mono control */
    right = left;

  retvol = left | (right << 8);

#if 1
  /* Scale volumes */
  left = mix_cvt[left];
  right = mix_cvt[right];

  /* Scale it again */
  left = mix_cvt[left];
  right = mix_cvt[right];
#endif

  if (ak_mix_devices[dev][LEFT_CHN].regno == 0xff)
    return OSS_EINVAL;

  devc->levels[dev] = retvol;


Set the left channel


  regoffs = ak_mix_devices[dev][LEFT_CHN].regno;
  val = 0;
  change_bits (devc, &val, dev, LEFT_CHN, left);
  ak_write (devc, regoffs, val);


Set the right channel


  if (ak_mix_devices[dev][RIGHT_CHN].regno == 0xff)
    return retvol;		/* Was just a mono channel */

  regoffs = ak_mix_devices[dev][RIGHT_CHN].regno;
  val = 0;
  change_bits (devc, &val, dev, RIGHT_CHN, right);
  ak_write (devc, regoffs, val);

  return retvol;
}

static void
apci_mixer_reset (apci_devc * devc)
{
  int i;

  for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
    if (MIXER_DEVS & (1 << i))
      apci_mixer_set (devc, i, devc->levels[i]);
  apci_set_recmask (devc, SOUND_MASK_MIC);
  devc->outsw1 = ak_reg_init[0x10];
  devc->outsw2 = ak_reg_init[0x11];
}

/*ARGSUSED*/
static int
apci_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg)
{
  apci_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:
	    val = *arg;
	    return *arg = apci_set_recmask (devc, val);
	    break;

	  default:
	    val = *arg;
	    return *arg = apci_mixer_set (devc, cmd & 0xff, val);
	  }
      else

Return parameters

	  {

	  case SOUND_MIXER_RECSRC:
	    return *arg = devc->recdevs;
	    break;

	  case SOUND_MIXER_DEVMASK:
	    return *arg = MIXER_DEVS;
	    break;

	  case SOUND_MIXER_STEREODEVS:
	    return *arg = STEREO_DEVS;
	    break;

	  case SOUND_MIXER_RECMASK:
	    return *arg = REC_DEVS;
	    break;

	  case SOUND_MIXER_CAPS:
	    return *arg = SOUND_CAP_EXCL_INPUT;
	    break;

	  default:
	    return *arg = apci_mixer_get (devc, cmd & 0xff);
	  }
    }
  else
    return OSS_EINVAL;
}

/*ARGSUSED*/
static int
getmute (apci_devc * devc, int offs, unsigned char bits, int value)
{
  unsigned char tmp;

  tmp = (offs == 0x10) ? devc->outsw1 : devc->outsw2;

  tmp &= bits;
  return (tmp == 0);		/* Note! inverted polarity */
}

static int
setmute (apci_devc * devc, int offs, unsigned char bits, int value)
{
  unsigned char tmp;

  value = !value;		/* Inverted polarity (now 0=mute) */

  tmp = (offs == 0x10) ? devc->outsw1 : devc->outsw2;

  tmp &= ~bits;			/* Mask old bits */
  if (value)
    tmp |= bits;

  ak_write (devc, offs, tmp);
  if (offs == 0x10)
    devc->outsw1 = tmp;
  else
    devc->outsw2 = tmp;

  return !value;
}

static int
apci_outsw (int dev, int ctrl, unsigned int cmd, int value)
{

Access function for AudioPCI mixer extension bits

  apci_devc *devc = mixer_devs[dev]->devc;

  if (cmd == SNDCTL_MIX_READ)
    {
      value = 0;
      switch (ctrl)
	{
	case 1:		/* 20 dB microphone boost */
	  value = devc->micboost;
	  break;

	case 2:		/* Microphone phantom power */
	  value = devc->micbias;
	  break;

	case 10:		/* Pcm mute */
	  value = getmute (devc, 0x11, 0x0c, value);
	  break;

	case 11:		/* Pcm2 mute */
	  value = getmute (devc, 0x10, 0x60, value);
	  break;

	case 12:		/* Mic mute */
	  value = getmute (devc, 0x10, 0x01, value);
	  break;

	case 13:		/* CD mute */
	  value = getmute (devc, 0x10, 0x06, value);
	  break;

	case 14:		/* Line mute */
	  value = getmute (devc, 0x10, 0x18, value);
	  break;

	case 15:		/* Line1 mute */
	  value = getmute (devc, 0x11, 0x30, value);
	  break;

	case 16:		/* Line2 mute */
	  value = getmute (devc, 0x11, 0x01, value);
	  break;

	case 17:		/* Line3 mute */
	  value = getmute (devc, 0x11, 0x02, value);
	  break;

	case 18:		/*Separate output enable for the synth device (XCTL0) */
	  value =
	    ((INB (devc->osdev, devc->base + CONC_bMISCCTL_OFF) & 0x01) != 0);
	  break;

	default:
	  return OSS_EINVAL;
	}

      return value;
    }

  if (cmd == SNDCTL_MIX_WRITE)
    {
      int tmp;

      if (value)
	value = 1;

      switch (ctrl)
	{
	case 1:		/* 20 dB microphone boost */
	  devc->micboost = value;
	  ak_write (devc, 0x19, value);
	  break;

	case 2:		/* Microphone phantom power */
	  devc->micbias = value;
	  /* Delay the actual change until next recording */
	  break;

	case 10:		/* Pcm mute */
	  value = setmute (devc, 0x11, 0x0c, value);
	  break;

	case 11:		/* Pcm2 mute */
	  value = setmute (devc, 0x10, 0x60, value);
	  break;

	case 12:		/* Mic mute */
	  value = setmute (devc, 0x10, 0x01, value);
	  break;

	case 13:		/* CD mute */
	  value = setmute (devc, 0x10, 0x06, value);
	  break;

	case 14:		/* Line mute */
	  value = setmute (devc, 0x10, 0x18, value);
	  break;

	case 15:		/* Line1 mute */
	  value = setmute (devc, 0x11, 0x30, value);
	  break;

	case 16:		/* Line2 mute */
	  value = setmute (devc, 0x11, 0x01, value);
	  break;

	case 17:		/* Line3 mute */
	  value = setmute (devc, 0x11, 0x02, value);
	  break;

	case 18:		/*Separate output enable for the synth device (XCTL0) */
	  tmp = INB (devc->osdev, devc->base + CONC_bMISCCTL_OFF);
	  if (value)
	    {
	      OUTB (devc->osdev, tmp | 0x01, devc->base + CONC_bMISCCTL_OFF);
	    }
	  else
	    {
	      OUTB (devc->osdev, tmp & ~0x01, devc->base + CONC_bMISCCTL_OFF);
	    }
	  break;

	default:
	  return OSS_EINVAL;
	}

      return value;
    }

  return OSS_EINVAL;
}

static int
apci_mix_init (int dev)
{
  int group, err;

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

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

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

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

  if ((err = mixer_ext_create_control (dev, group, 10, apci_outsw,
				       MIXT_ONOFF,
				       "APCI_PCMMUTE", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group, 11, apci_outsw,
				       MIXT_ONOFF,
				       "APCI_PCM2MUTE", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group, 12, apci_outsw,
				       MIXT_ONOFF,
				       "APCI_MICMUTE", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group, 13, apci_outsw,
				       MIXT_ONOFF,
				       "APCI_CDMUTE", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group, 14, apci_outsw,
				       MIXT_ONOFF,
				       "APCI_LINEMUTE", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group, 15, apci_outsw,
				       MIXT_ONOFF,
				       "APCI_LINE1MUTE", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group, 16, apci_outsw,
				       MIXT_ONOFF,
				       "APCI_LINE2MUTE", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group, 17, apci_outsw,
				       MIXT_ONOFF,
				       "APCI_LINE3MUTE", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

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

  if ((err = mixer_ext_create_control (dev, group, 18, apci_outsw,
				       MIXT_ONOFF,
				       "APCI4CH_ENABLE", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  return 0;
}

static mixer_driver_t apci_mixer_driver = {
  apci_mixer_ioctl
};

static int
apciintr (oss_device_t * osdev)
{
  int stats, i;
  unsigned char ackbits = 0, tmp;
  unsigned char uart_stat;
  apci_devc *devc = (apci_devc *) osdev->devc;
  apci_portc *portc;
  int serviced = 0;

  stats = INL (devc->osdev, devc->base + 0x04);

  if (!(stats & 0x80000000))	/* No interrupt pending */
    return 0;

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

      if (stats & 0x00000010)	/* CCB interrupt */
	{
	  cmn_err (CE_WARN, "CCB interrupt\n");
	}

      if ((stats & 0x00000004) && (portc->atype))	/* DAC1 (synth) interrupt */
	{
	  ackbits |= CONC_SERCTL_SYNIE;
	  if (portc->trigger_bits & PCM_ENABLE_OUTPUT)
	    oss_audio_outputintr (portc->audiodev, 0);

	}

      if ((stats & 0x00000002) && (!portc->atype))	/* DAC2 interrupt */
	{
	  ackbits |= CONC_SERCTL_DACIE;
	  if (portc->trigger_bits & PCM_ENABLE_OUTPUT)
	    oss_audio_outputintr (portc->audiodev, 0);
	}

      if ((stats & 0x00000001) && (!portc->atype))	/* ADC interrupt */
	{
	  ackbits |= CONC_SERCTL_ADCIE;
	  if (portc->trigger_bits & PCM_ENABLE_INPUT)
	    oss_audio_inputintr (portc->audiodev, 0);
	}

      if (stats & 0x00000008)	/* UART interrupt */
	{
	  uart_stat = INB (devc->osdev, devc->base + CONC_bUARTCSTAT_OFF);

	  while (uart_stat & CONC_UART_RXRDY)
	    {
	      unsigned char d;

	      d = INB (devc->osdev, devc->base + CONC_bUARTDATA_OFF);

	      if (devc->midi_opened & OPEN_READ && devc->midi_input_intr)
		devc->midi_input_intr (devc->midi_dev, d);
	      uart_stat = INB (devc->osdev, devc->base + CONC_bUARTCSTAT_OFF);
	    }
	}

      tmp = INB (devc->osdev, devc->base + CONC_bSERCTL_OFF);
      OUTB (devc->osdev, (tmp & ~ackbits), devc->base + CONC_bSERCTL_OFF);	/* Clear bits */
      OUTB (devc->osdev, tmp | ackbits, devc->base + CONC_bSERCTL_OFF);	/* Return them back on */

    }
  return serviced;
}


Audio routines


static unsigned short
compute_dac2_rate (int samPerSec)
{

  unsigned short usTemp;

  /* samPerSec /= 2; */

  usTemp = (unsigned short) ((DAC_CLOCK_DIVIDE / 8) / samPerSec);

  if (usTemp & 0x00000001)
    {
      usTemp >>= 1;
      usTemp -= 1;
    }
  else
    {
      usTemp >>= 1;
      usTemp -= 2;
    }

  return usTemp;

}

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

  int speeds[] = { 5512, 11025, 22050, 44100 };
  int i, n = 0, best = 1000000;


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

  if (portc->atype)
    {
      if (arg > 44100)
	arg = 44100;
      if (arg < 5512)
	arg = 5512;

      for (i = 0; i < 4; i++)
	{
	  int diff = arg - speeds[i];

	  if (diff < 0)
	    diff *= -1;

	  if (diff < best)
	    {
	      n = i;
	      best = diff;
	    }
	}
      portc->speed = speeds[n];
      portc->speedsel = n;
    }
  else
    {
      if (arg > 48000)
	arg = 48000;
      if (arg < 5000)
	arg = 5000;
      portc->speed = arg;
    }
  return portc->speed;
}

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

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

  return portc->channels;
}

static unsigned int
apci_audio_set_format (int dev, unsigned int arg)
{
  apci_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
apci_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  return OSS_EINVAL;
}

static void apci_audio_trigger (int dev, int state);

static void
apci_audio_reset (int dev)
{
  apci_audio_trigger (dev, 0);
}

static void
apci_audio_reset_input (int dev)
{
  apci_portc *portc = audio_engines[dev]->portc;
  apci_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
apci_audio_reset_output (int dev)
{
  apci_portc *portc = audio_engines[dev]->portc;
  apci_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

/*ARGSUSED*/
static int
apci_audio_open (int dev, int mode, int open_flags)
{
  apci_portc *portc = audio_engines[dev]->portc;
  apci_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;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

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

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

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

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

}

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

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

}

static void
apci_audio_trigger (int dev, int state)
{
  apci_devc *devc = audio_engines[dev]->devc;
  apci_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))
	    {
	      if (portc->atype)
		{
		  tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF);
		  tmp |= CONC_DEVCTL_DAC1_EN;
		  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);
		}
	      else
		{
		  tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF);
		  tmp |= CONC_DEVCTL_DAC2_EN;
		  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);
		}
	      portc->trigger_bits |= PCM_ENABLE_OUTPUT;
	      oss_udelay (50);
	    }
	}
      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;
	      if (portc->atype)
		{
		  tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF);
		  tmp &= ~CONC_DEVCTL_DAC1_EN;
		  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);

		  tmp = INB (devc->osdev, devc->base + CONC_bSERCTL_OFF);
		  tmp &= ~CONC_SERCTL_SYNIE;
		  OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);
		}
	      else
		{
		  tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF);
		  tmp &= ~CONC_DEVCTL_DAC2_EN;
		  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);

		  tmp = INB (devc->osdev, devc->base + CONC_bSERCTL_OFF);
		  tmp &= ~CONC_SERCTL_DACIE;
		  OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);
		}
	    }
	}
    }

  if ((portc->open_mode & OPEN_READ)
      && !(audio_engines[dev]->flags & ADEV_NOINPUT))
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF);
	      tmp |= CONC_DEVCTL_ADC_EN;
	      OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);
	      portc->trigger_bits |= PCM_ENABLE_INPUT;
	      oss_udelay (50);
	    }
	}
      else
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      portc->audio_enabled &= ~PCM_ENABLE_INPUT;
	      portc->trigger_bits &= ~PCM_ENABLE_INPUT;

	      tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF);
	      tmp &= ~CONC_DEVCTL_ADC_EN;
	      OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);

	      tmp = INB (devc->osdev, devc->base + CONC_bSERCTL_OFF);
	      tmp &= ~CONC_SERCTL_ADCIE;
	      OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);
	    }
	}
    }

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

/*ARGSUSED*/
static int
apci_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  apci_devc *devc = audio_engines[dev]->devc;
  apci_portc *portc = audio_engines[dev]->portc;
  unsigned short tmp = 0x00;
  oss_native_word flags;

  /* Set physical address of the DMA buffer */

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  apci_writemem (devc, CONC_ADCCTL_PAGE, CONC_dADCPADDR_OFF,
		 dmap->dmabuf_phys);

  /* Set DAC (ADC) rate */
  OUTW (devc->osdev, compute_dac2_rate (portc->speed),
	devc->base + CONC_wDACRATE_OFF);

  /* Set format */
  tmp = INB (devc->osdev, devc->base + CONC_bSERFMT_OFF);
  tmp &= ~(CONC_PCM_ADC_STEREO | CONC_PCM_ADC_16BIT);
  if (portc->channels == 2)
    tmp |= CONC_PCM_ADC_STEREO;
  if (portc->bits == 16)
    {
      tmp |= CONC_PCM_ADC_16BIT;
      OUTB (devc->osdev, 0x10, devc->base + CONC_bSKIPC_OFF);	/* Skip count register */
    }
  else
    {
      OUTB (devc->osdev, 0x08, devc->base + CONC_bSKIPC_OFF);	/* Skip count register */
    }
  OUTB (devc->osdev, tmp, devc->base + CONC_bSERFMT_OFF);

  /* Set the frame count */
  apci_writemem (devc, CONC_ADCCTL_PAGE, CONC_wADCFC_OFF,
		 (dmap->bytes_in_use / 4) - 1);

  /* Set # of samples between interrupts */
  OUTW (devc->osdev,
	(dmap->fragment_size / ((portc->channels * portc->bits) / 8)) - 1,
	devc->base + CONC_wADCIC_OFF);

  /* Enable the wave interrupt */
  tmp = INB (devc->osdev, devc->base + CONC_bSERCTL_OFF) & ~CONC_SERCTL_ADCIE;
  OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);
  tmp |= CONC_SERCTL_ADCIE;
  OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);
  OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);

  /* Enable microphone phantom power */
  tmp = INW (devc->osdev, devc->base + 2) & ~CONC_DEVCTL_MICBIAS;
  if (devc->micbias)
    tmp |= CONC_DEVCTL_MICBIAS;
  OUTW (devc->osdev, tmp, devc->base + 2);

  portc->audio_enabled &= ~PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

/*ARGSUSED*/
static int
apci_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  dmap_t *dmap = audio_engines[dev]->dmap_out;
  unsigned char tmp = 0x00;
  apci_devc *devc = audio_engines[dev]->devc;
  apci_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  if (portc->atype)
    {
      /* Set physical address of the DMA buffer */
      apci_writemem (devc, CONC_SYNCTL_PAGE, CONC_dSYNPADDR_OFF,
		     dmap->dmabuf_phys);

      /* Set DAC1 rate */
      tmp = INB (devc->osdev, devc->base + CONC_bMISCCTL_OFF) & ~0x30;
      tmp |= portc->speedsel << 4;
      OUTB (devc->osdev, tmp, devc->base + CONC_bMISCCTL_OFF);

      /* Set format */
      tmp = INB (devc->osdev, devc->base + CONC_bSERFMT_OFF);
      tmp &= ~(CONC_PCM_DAC1_STEREO | CONC_PCM_DAC1_16BIT);
      if (portc->channels == 2)
	tmp |= CONC_PCM_DAC1_STEREO;
      if (portc->bits == 16)
	{
	  tmp |= CONC_PCM_DAC1_16BIT;
	}
      OUTB (devc->osdev, tmp, devc->base + CONC_bSERFMT_OFF);

      /* Set the frame count */
      apci_writemem (devc, CONC_SYNCTL_PAGE, CONC_wSYNFC_OFF,
		     (dmap->bytes_in_use / 4) - 1);

      /* Set # of samples between interrupts */
      OUTW (devc->osdev,
	    (dmap->fragment_size / ((portc->channels * portc->bits) / 8)) - 1,
	    devc->base + CONC_wSYNIC_OFF);

      /* Enable the wave interrupt */
      tmp =
	INB (devc->osdev, devc->base + CONC_bSERCTL_OFF) & ~CONC_SERCTL_SYNIE;
      OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);
      tmp |= CONC_SERCTL_SYNIE;
      OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);
    }
  else
    {
      /* Set physical address of the DMA buffer */
      apci_writemem (devc, CONC_DACCTL_PAGE, CONC_dDACPADDR_OFF,
		     dmap->dmabuf_phys);

      /* Set DAC rate */
      OUTW (devc->osdev, compute_dac2_rate (portc->speed),
	    devc->base + CONC_wDACRATE_OFF);

      /* Set format */
      tmp = INB (devc->osdev, devc->base + CONC_bSERFMT_OFF);
      tmp &= ~(CONC_PCM_DAC2_STEREO | CONC_PCM_DAC2_16BIT);
      if (portc->channels == 2)
	tmp |= CONC_PCM_DAC2_STEREO;
      if (portc->bits == 16)
	{
	  tmp |= CONC_PCM_DAC2_16BIT;
	  OUTB (devc->osdev, 0x10, devc->base + CONC_bSKIPC_OFF);	/* Skip count register */
	}
      else
	{
	  OUTB (devc->osdev, 0x08, devc->base + CONC_bSKIPC_OFF);	/* Skip count register */
	}
      OUTB (devc->osdev, tmp, devc->base + CONC_bSERFMT_OFF);

      /* Set the frame count */
      apci_writemem (devc, CONC_DACCTL_PAGE, CONC_wDACFC_OFF,
		     (dmap->bytes_in_use / 4) - 1);
      apci_writemem (devc, CONC_DACCTL_PAGE, CONC_wDACFC_OFF,
		     (dmap->bytes_in_use / 4) - 1);

      /* Set # of samples between interrupts */
      OUTW (devc->osdev,
	    (dmap->fragment_size / ((portc->channels * portc->bits) / 8)) - 1,
	    devc->base + CONC_wDACIC_OFF);

      /* Enable the wave interrupt */
      tmp =
	INB (devc->osdev, devc->base + CONC_bSERCTL_OFF) & ~CONC_SERCTL_DACIE;
      OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);
      tmp |= CONC_SERCTL_DACIE;
      OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);
    }

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

/*ARGSUSED*/
static int
apci_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  apci_devc *devc = audio_engines[dev]->devc;
  apci_portc *portc = audio_engines[dev]->portc;
  int ptr = 0, port = 0, page = 0;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  if (direction == PCM_ENABLE_OUTPUT)
    {
      if (portc->atype)
	{
	  port = CONC_wSYNFC_OFF;
	  page = CONC_SYNCTL_PAGE;
	}
      else
	{
	  port = CONC_wDACFC_OFF;
	  page = CONC_DACCTL_PAGE;
	}
    }

  if (direction == PCM_ENABLE_INPUT)
    {
      port = CONC_wADCFC_OFF;
      page = CONC_ADCCTL_PAGE;
    }

  ptr = apci_readmem (devc, page, port);
  ptr >>= 16;
  ptr <<= 2;			/* count is in dwords */
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return ptr;
}


static const audiodrv_t apci_audio_driver = {
  apci_audio_open,
  apci_audio_close,
  apci_audio_output_block,
  apci_audio_start_input,
  apci_audio_ioctl,
  apci_audio_prepare_for_input,
  apci_audio_prepare_for_output,
  apci_audio_reset,
  NULL,
  NULL,
  apci_audio_reset_input,
  apci_audio_reset_output,
  apci_audio_trigger,
  apci_audio_set_rate,
  apci_audio_set_format,
  apci_audio_set_channels,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,				/* apci_alloc_buffer */
  NULL,				/* apci_free_buffer */
  NULL,
  NULL,
  apci_get_buffer_pointer
};

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

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

  devc->midi_input_intr = inputbyte;
  devc->midi_opened = mode;

  if (mode & OPEN_READ)
    {
      OUTB (devc->osdev, CONC_UART_RXINTEN, devc->base + CONC_bUARTCSTAT_OFF);
    }

  return 0;
}

/*ARGSUSED*/
static void
apci_midi_close (int dev, int mode)
{
  apci_devc *devc = (apci_devc *) midi_devs[dev]->devc;

  OUTB (devc->osdev, 0x00, devc->base + CONC_bUARTCSTAT_OFF);
  devc->midi_opened = 0;
}

static int
apci_midi_out (int dev, unsigned char midi_byte)
{
  apci_devc *devc = (apci_devc *) midi_devs[dev]->devc;
  int i;

  unsigned char uart_stat =
    INB (devc->osdev, devc->base + CONC_bUARTCSTAT_OFF);

  bmast_off (devc);
  for (i = 0; i < 30000; i++)
    {
      uart_stat = INB (devc->osdev, devc->base + CONC_bUARTCSTAT_OFF);
      if (uart_stat & CONC_UART_TXRDY)
	break;
    }

  if (!(uart_stat & CONC_UART_TXRDY))
    {
      bmast_on (devc);
      return 0;
    }


  OUTB (devc->osdev, midi_byte, devc->base + CONC_bUARTDATA_OFF);
  bmast_on (devc);

  return 1;
}

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

static midi_driver_t apci_midi_driver = {
  apci_midi_open,
  apci_midi_close,
  apci_midi_ioctl,
  apci_midi_out,
};

static int
init_apci (apci_devc * devc)
{
  int i, my_mixer, tmp;

  devc->micbias = 1;
  devc->micboost = 1;

  OUTW (devc->osdev, compute_dac2_rate (8000),
	devc->base + CONC_wDACRATE_OFF);

  tmp =
    INB (devc->osdev,
	 devc->base + CONC_bMISCCTL_OFF) & ~CONC_MISCTL_CCB_INTRM;
  tmp |= CONC_MISCTL_MUTE | CONC_MISCTL_DAC1FREQ_2205;
  OUTB (devc->osdev, tmp, devc->base + CONC_bMISCCTL_OFF);

  /* Turn on UART, CODEC and joystick. Disable SERR. */
  tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF) & ~CONC_DEVCTL_SERR_DISABLE;	/* Yes */
  tmp |= CONC_DEVCTL_UART_EN | CONC_DEVCTL_CODEC_EN | CONC_DEVCTL_JSTICK_EN;
  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);

  /* Disable NMI */
  OUTB (devc->osdev, 0x00, devc->base + CONC_bNMIENA_OFF);
  OUTW (devc->osdev, 0x0000, devc->base + CONC_wNMISTAT_OFF);

  /* Init serial interface */
  OUTB (devc->osdev, 0x00, devc->base + CONC_bSERCTL_OFF);
  OUTB (devc->osdev, CONC_PCM_DAC1_STEREO | CONC_PCM_DAC1_16BIT,
	devc->base + CONC_bSERFMT_OFF);

  /* Unmute the codec */
  tmp = INB (devc->osdev, devc->base + CONC_bMISCCTL_OFF) & ~CONC_MISCTL_MUTE;
  OUTB (devc->osdev, tmp, devc->base + CONC_bMISCCTL_OFF);

  /* Reset the UART */
  OUTB (devc->osdev, 0x03, devc->base + CONC_bUARTCSTAT_OFF);
  OUTB (devc->osdev, 0x00, devc->base + CONC_bUARTCSTAT_OFF);



Mixer initialization *********

  oss_udelay (30);
  ak_write (devc, 0x16, 0x03);	/* Release reset */
  oss_udelay (50);
  ak_write (devc, 0x18, 0x00);	/* Select ADC from input mixer */

  for (i = 0; i <= 0x19; i++)
    ak_write (devc, i, ak_reg_init[i]);

  /* Enable microphone phantom power */
  tmp = INW (devc->osdev, devc->base + 2) & ~CONC_DEVCTL_MICBIAS;
  if (devc->micbias)
    tmp |= CONC_DEVCTL_MICBIAS;
  OUTW (devc->osdev, tmp, devc->base + 2);

  if ((my_mixer = oss_install_mixer (OSS_MIXER_DRIVER_VERSION,
				     devc->osdev,
				     devc->osdev,
				     "Creative AudioPCI",
				     &apci_mixer_driver,
				     sizeof (mixer_driver_t), devc)) >= 0)
    {
      char mxname[20];

      sprintf (mxname, "AudioPCI");
      devc->recdevs = 0;
      apci_set_recmask (devc, SOUND_MASK_MIC);
      devc->levels = load_mixer_volumes (mxname, default_mixer_levels, 1);
      mixer_ext_set_init_fn (my_mixer, apci_mix_init, 30);
      apci_mixer_reset (devc);
    }

  for (i = 0; i < MAX_PORTC; i++)
    {

      int adev;
      char tmp_name[100];
      apci_portc *portc = &devc->portc[i];
      int caps = ADEV_AUTOMODE;

      if (i == 0)
	{
	  strcpy (tmp_name, devc->chip_name);
	  caps |= ADEV_DUPLEX;
	}
      else
	{
	  sprintf (tmp_name, "%s (playback only)", devc->chip_name);
	  caps |= ADEV_NOINPUT;
	}

      if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					devc->osdev,
					devc->osdev,
					tmp_name,
					&apci_audio_driver,
					sizeof (audiodrv_t),
					caps,
					AFMT_U8 | AFMT_S16_LE, devc, -1)) < 0)
	{
	  adev = -1;
	  return 0;
	}
      else
	{
	  audio_engines[adev]->portc = portc;
	  if (i == 0)
	    {
	      audio_engines[adev]->min_rate = 5000;
	      audio_engines[adev]->max_rate = 48000;
	      audio_engines[adev]->caps |= PCM_CAP_FREERATE;
	    }
	  else
	    {
	      audio_engines[adev]->min_rate = 5012;
	      audio_engines[adev]->max_rate = 44100;
	    }
/*        audio_engines[adev]->min_block = 1024; */
	  portc->open_mode = 0;
	  portc->audiodev = adev;
	  portc->atype = i;
          audio_engines[adev]->mixer_dev = my_mixer;
#ifdef CONFIG_OSS_VMIX
	  if (i == 0)
	     vmix_attach_audiodev(devc->osdev, adev, -1, 0);
#endif
	}
    }

  if ((devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "AUDIOPCI", "AudioPCI UART", &apci_midi_driver, sizeof (midi_driver_t),
					     0, devc, devc->osdev)) < 0)
    {
      cmn_err (CE_WARN, "Couldn't install MIDI device\n");
      return 0;
    }

  devc->midi_opened = 0;
  return 1;
}

int
oss_audiopci_attach (oss_device_t * osdev)
{
  unsigned char pci_irq_line, pci_revision;
  unsigned short pci_command, vendor, device;
  unsigned int pci_ioaddr;
  int err;
  apci_devc *devc;

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

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

  if ((vendor != ENSONIQ_VENDOR_ID && vendor != ENSONIQ_VENDOR_ID2) ||
      device != ENSONIQ_AUDIOPCI)

    return 0;

  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_irq_line == 0)
    {
      cmn_err (CE_WARN, "IRQ not assigned by BIOS (%d). Can't continue\n",
	       pci_irq_line);
      return 0;
    }

  if (pci_ioaddr == 0)
    {
      cmn_err (CE_WARN, "I/O address 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->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr);
  /* Remove I/O space marker in bit 0. */
  devc->base &= ~3;

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

  devc->chip_name = "Creative AudioPCI (ES1370)";

  oss_register_device (osdev, devc->chip_name);


  if ((err = oss_register_interrupts (osdev, 0, apciintr, NULL)) < 0)
    {
      cmn_err (CE_WARN, "Can't allocate IRQ%d, err=%d\n", pci_irq_line, err);
      return 0;
    }

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

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


int
oss_audiopci_detach (oss_device_t * osdev)
{
  apci_devc *devc = (apci_devc *) osdev->devc;
  int tmp;

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

  tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF) &
    ~(CONC_DEVCTL_DAC2_EN | CONC_DEVCTL_ADC_EN | CONC_DEVCTL_DAC1_EN);
  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);
  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);
  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);
  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);

  oss_unregister_interrupts (devc->osdev);

  MUTEX_CLEANUP (devc->mutex);
  MUTEX_CLEANUP (devc->low_mutex);
  UNMAP_PCI_IOADDR (devc->osdev, 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