Google
 

Open Sound System
OSS 4.0 Internals

ich/ich.c

Driver for the Intel ICH AC97 audio controller

Description



The same design is also used in many PC chipsets by nVidia, AMD and SiS for audio functionality.



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

#include <ac97.h>
extern int intelpci_force_mmio;

#define INTEL_VENDOR_ID         0x8086
#define SIS_VENDOR_ID           0x1039
#define NVIDIA_VENDOR_ID        0x10de
#define AMD_VENDOR_ID           0x1022
#define SIS_DEVICE_7012         0x7012
#define INTEL_DEVICE_ICH1       0x2415
#define INTEL_DEVICE_ICH1R1     0x2425
#define INTEL_DEVICE_ICH1R2     0x7195
#define INTEL_DEVICE_ICH2       0x2445
#define INTEL_DEVICE_ICH3       0x2485
#define INTEL_DEVICE_ICH4       0x24c5
#define INTEL_DEVICE_ICH5       0x24d5
#define INTEL_DEVICE_ESB        0x25a6
#define INTEL_DEVICE_ICH6       0x266e
#define INTEL_DEVICE_ICH7       0x27de
#define NVIDIA_DEVICE_NFORCE    0x01b1
#define NVIDIA_DEVICE_MCP4	0x003a
#define NVIDIA_DEVICE_NFORCE2   0x006a
#define	NVIDIA_DEVICE_CK8	0x008a
#define NVIDIA_DEVICE_NFORCE3   0x00da
#define NVIDIA_DEVICE_CK8S      0x00ea
#define	NVIDIA_DEVICE_NFORCE4	0x0059
#define	NVIDIA_DEVICE_MCP51	0x026b
#define AMD_DEVICE_768          0x7445
#define AMD_DEVICE_8111         0x746d

extern int intelpci_rate_tuning;

#define MAX_PORTC 3
#define BDL_SIZE	32

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

typedef struct
{
  unsigned int addr;
  unsigned short size;
  unsigned short flags;
}
bdl_t;

typedef struct ich_devc
{
  oss_device_t *osdev;
  oss_native_word base, ac97_base;
  oss_native_word membar_addr, ac97_membar_addr;
  char *membar_virt, *ac97_membar_virt;
#define CTL_BASE   0		/* addressing controller regs */
#define MIXER_BASE 1		/* addressing mixer regs */
  int mem_mode;
#define MMAP_MODE  0		/* ICH4/ICH5 uses MEM BARS */
#define IO_MODE    1		/* ICH1/2/3/4/5 uses IO BARS */

  int irq;
  oss_mutex_t mutex;
  oss_mutex_t low_mutex;

  /* Mixer */
  ac97_devc ac97devc;
  int mixer_dev;

  /* Audio parameters */
  int open_mode;
  int fifo_errors;

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

  int play_currbuf, play_currfrag;
  int spdif_currbuf, spdif_currfrag;
  int rec_currbuf, rec_currfrag;
#define INTEL_ICH1 0
#define INTEL_ICH3 1
#define INTEL_ICH4 2
#define SIS_7012   3
#define AMD_768    4
#define AMD_8111   5
#define NVIDIA_NFORCE  6
#define NVIDIA_NFORCE2 7
  int model;
  char *chip_name;
  ich_portc portc[MAX_PORTC];
  int play_frag_index[BDL_SIZE];
  int rec_frag_index[BDL_SIZE];
  int spdif_frag_index[BDL_SIZE];
}
ich_devc;

static unsigned int
ich_INL (ich_devc * devc, int base, unsigned int a)
{
  if (devc->mem_mode == MMAP_MODE)
    {
      if (base == CTL_BASE)
	return *(volatile unsigned int *) (devc->membar_virt + (a));
      else
	return *(volatile unsigned int *) (devc->ac97_membar_virt + (a));
    }
  else
    {
      if (base == CTL_BASE)
	return INL (devc->osdev, devc->base + a);
      else
	return INL (devc->osdev, devc->ac97_base + a);
    }
}

static unsigned short
ich_INW (ich_devc * devc, int base, unsigned short a)
{
  if (devc->mem_mode == MMAP_MODE)
    {
      if (base == CTL_BASE)
	return *(volatile unsigned short *) (devc->membar_virt + (a));
      else
	return *(volatile unsigned short *) (devc->ac97_membar_virt + (a));
    }
  else
    {
      if (base == CTL_BASE)
	return INW (devc->osdev, devc->base + a);
      else
	return INW (devc->osdev, devc->ac97_base + a);
    }
}

static unsigned char
ich_INB (ich_devc * devc, int base, unsigned char a)
{
  if (devc->mem_mode == MMAP_MODE)
    {
      if (base == CTL_BASE)
	return *(volatile unsigned char *) (devc->membar_virt + (a));
      else
	return *(volatile unsigned char *) (devc->ac97_membar_virt + (a));
    }
  else
    {
      if (base == CTL_BASE)
	return INB (devc->osdev, devc->base + a);
      else
	return INB (devc->osdev, devc->ac97_base + a);
    }
}

static void
ich_OUTL (ich_devc * devc, unsigned int d, int base, unsigned int a)
{
  if (devc->mem_mode == MMAP_MODE)
    {
      if (base == CTL_BASE)
	*(volatile unsigned int *) (devc->membar_virt + (a)) = d;
      else
	*(volatile unsigned int *) (devc->ac97_membar_virt + (a)) = d;
    }
  else
    {
      if (base == CTL_BASE)
	OUTL (devc->osdev, d, devc->base + a);
      else
	OUTL (devc->osdev, d, devc->ac97_base + a);
    }
}

static void
ich_OUTW (ich_devc * devc, unsigned short d, int base, unsigned short a)
{
  if (devc->mem_mode == MMAP_MODE)
    {
      if (base == CTL_BASE)
	*(volatile unsigned short *) (devc->membar_virt + (a)) = d;
      else
	*(volatile unsigned short *) (devc->ac97_membar_virt + (a)) = d;
    }
  else
    {
      if (base == CTL_BASE)
	OUTW (devc->osdev, d, devc->base + a);
      else
	OUTW (devc->osdev, d, devc->ac97_base + a);
    }
}

static void
ich_OUTB (ich_devc * devc, unsigned char d, int base, unsigned char a)
{
  if (devc->mem_mode == MMAP_MODE)
    {
      if (base == CTL_BASE)
	*(volatile unsigned char *) (devc->membar_virt + (a)) = d;
      else
	*(volatile unsigned char *) (devc->ac97_membar_virt + (a)) = d;
    }
  else
    {
      if (base == CTL_BASE)
	OUTB (devc->osdev, d, devc->base + a);
      else
	OUTB (devc->osdev, d, devc->ac97_base + a);
    }
}

static int
ac97_read (void *devc_, int reg)
{
  ich_devc *devc = devc_;
  int i = 100, status;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  status = ich_INB (devc, CTL_BASE, 0x34);

  while (status & 0x01 && i-- > 0)
    {
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      oss_udelay (10);
      MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
      status = ich_INB (devc, CTL_BASE, 0x34);
    }

  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return ich_INW (devc, MIXER_BASE, reg);
}

static int
ac97_write (void *devc_, int reg, int data)
{
  ich_devc *devc = devc_;
  int i = 100, status;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  status = ich_INB (devc, CTL_BASE, 0x34);

  while (status & 0x01 && i-- > 0)
    {
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      oss_udelay (10);
      MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
      status = ich_INB (devc, CTL_BASE, 0x34);
    }

  ich_OUTW (devc, data, MIXER_BASE, reg);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 1;
}


The top half interrupt handler

static int
ichintr (oss_device_t * osdev)
{
  int serviced = 0;
  ich_devc *devc = osdev->devc;
  ich_portc *portc;
  unsigned int glob_status, status, p, f;
  oss_native_word flags;
  int i;


  flags = 0;			/* To prevent compiler warnings */
  MUTEX_ENTER (devc->mutex, flags);
  /* Get pending interrupts and acknowledge them */
  glob_status = ich_INL (devc, CTL_BASE, 0x30);
  ich_OUTL (devc, glob_status, CTL_BASE, 0x30);


Check the interrupt bits of the status register

  if (!(glob_status & 0x0cf7))
    {
      /* Not for me */
      MUTEX_EXIT (devc->mutex, flags);
      return 0;
    }

  /*-------------------- Handle PCM -------------------*/
  if (devc->model == SIS_7012)
    status = ich_INB (devc, CTL_BASE, 0x18);
  else
    status = ich_INB (devc, CTL_BASE, 0x16);

  if (status & 0x10)		/* FIFO error */
    devc->fifo_errors++;

  if (status & 0x08)
    {
      for (i = 0; i < MAX_PORTC - 1; i++)
	{
	  portc = &devc->portc[i];
	  serviced = 1;
	  if ((portc->trigger_bits & PCM_ENABLE_OUTPUT))	/* IOC interrupt */
	    {
	      dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out;
	      p = ich_INB (devc, CTL_BASE, 0x14);

	      if (p != devc->play_currbuf)
		{
		  p = devc->play_currbuf;
		  f = devc->play_currfrag;
		  devc->playBDL[p].addr =
		    dmap->dmabuf_phys + (f * dmap->fragment_size);

		  /* SIS uses bytes, intelpci uses samples */
		  if (devc->model == SIS_7012)
		    devc->playBDL[p].size = (dmap->fragment_size);
		  else
		    devc->playBDL[p].size = (dmap->fragment_size / 2);
		  devc->playBDL[p].flags = 0xc000;	/* IOC interrupts */

		  ich_OUTB (devc, p, CTL_BASE, 0x15);	/* Set LVD */
		  devc->play_frag_index[p] = f;
		  devc->play_currbuf = (p + 1) % BDL_SIZE;
		  devc->play_currfrag = (f + 1) % dmap->nfrags;
		}
	      oss_audio_outputintr (portc->audiodev, 1);
	    }
	}
      if (devc->model == SIS_7012)
	ich_OUTB (devc, status, CTL_BASE, 0x18);	/* Clear interrupts */
      else
	ich_OUTB (devc, status, CTL_BASE, 0x16);	/* Clear interrupts */
    }

  /*------------------- handle SPDIF interrupts -------------------------*/

  if (devc->model == NVIDIA_NFORCE2)
    {
      status = ich_INB (devc, CTL_BASE, 0x76);
      if (status & 0x08)
	{
	  portc = &devc->portc[2];
	  serviced = 1;
	  if ((portc->trigger_bits & PCM_ENABLE_OUTPUT))	/* IOC interrupt */
	    {
	      dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out;
	      p = ich_INB (devc, CTL_BASE, 0x74);

	      if (p != devc->spdif_currbuf)
		{
		  p = devc->spdif_currbuf;
		  f = devc->spdif_currfrag;
		  devc->spdifBDL[p].addr =
		    dmap->dmabuf_phys + (f * dmap->fragment_size);
		  /* SIS uses bytes, intelpci uses samples */
		  devc->spdifBDL[p].size = (dmap->fragment_size / 2);
		  devc->spdifBDL[p].flags = 0xc000;	/* IOC interrupts */

		  ich_OUTB (devc, p, CTL_BASE, 0x75);	/* Set LVD */
		  devc->spdif_frag_index[p] = f;
		  devc->spdif_currbuf = (p + 1) % BDL_SIZE;
		  devc->spdif_currfrag = (f + 1) % dmap->nfrags;
		}
	      oss_audio_outputintr (portc->audiodev, 1);
	    }
	  ich_OUTB (devc, status, CTL_BASE, 0x76);	/* Clear interrupts */
	}
    }

  /*----------------------- Handle Recording Interrupts --------------------*/

  if (devc->model == SIS_7012)
    status = ich_INB (devc, CTL_BASE, 0x08);
  else
    status = ich_INB (devc, CTL_BASE, 0x06);

  if (status & 0x08)
    {
      for (i = 0; i < MAX_PORTC - 1; i++)
	{
	  portc = &devc->portc[i];
	  serviced = 1;
	  if ((portc->trigger_bits & PCM_ENABLE_INPUT))	/* IOC interrupt */
	    {
	      dmap_t *dmap = audio_engines[portc->audiodev]->dmap_in;
	      p = ich_INB (devc, CTL_BASE, 0x04);

	      if (p != devc->rec_currbuf)
		{
		  p = devc->rec_currbuf;
		  f = devc->rec_currfrag;
		  devc->recBDL[p].addr =
		    dmap->dmabuf_phys + (f * dmap->fragment_size);

		  /* SIS uses bytes, intelpci uses samples */
		  if (devc->model == SIS_7012)
		    devc->recBDL[p].size = (dmap->fragment_size);
		  else
		    devc->recBDL[p].size = (dmap->fragment_size / 2);

		  devc->recBDL[p].flags = 0xc000;	/* IOC interrupts */

		  ich_OUTB (devc, p, CTL_BASE, 0x05);	/* Set LVD */
		  devc->rec_frag_index[p] = f;
		  devc->rec_currbuf = (p + 1) % BDL_SIZE;
		  devc->rec_currfrag = (f + 1) % dmap->nfrags;
		}
	      oss_audio_inputintr (portc->audiodev, 0);
	    }
	}
      if (devc->model == SIS_7012)
	ich_OUTB (devc, status, CTL_BASE, 0x08);	/* Clear int */
      else
	ich_OUTB (devc, status, CTL_BASE, 0x06);	/* Clear int */
    }
  MUTEX_EXIT (devc->mutex, flags);
  return serviced;
}



Audio routines


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

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

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

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

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

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

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

  return portc->channels;
}

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

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

#if 1
  if (portc->open_mode & OPEN_READ)
    return portc->bits = AFMT_S16_LE;
#endif
  if (!(arg & (AFMT_S16_LE | AFMT_AC3)))
    return portc->bits;
  portc->bits = arg;

  return portc->bits;
}

/*ARGSUSED*/
static int
ich_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  return -EINVAL;
}

static void ich_audio_trigger (int dev, int state);

static void
ich_audio_reset (int dev)
{
  ich_audio_trigger (dev, 0);
}

static void
ich_audio_reset_input (int dev)
{
  ich_portc *portc = audio_engines[dev]->portc;
  ich_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
ich_audio_reset_output (int dev)
{
  ich_portc *portc = audio_engines[dev]->portc;
  ich_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

/*ARGSUSED*/
static int
ich_audio_open (int dev, int mode, int openflags)
{
  ich_portc *portc = audio_engines[dev]->portc;
  ich_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 -EBUSY;
    }
  if (portc->port_type == DF_SPDIF)
    {
      if (mode & OPEN_READ)
	{
	  cmn_err (CE_NOTE, "The S/PDIF device supports only playback\n");
	  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
	  return -EIO;
	}
    }
  else
    {
      if (devc->open_mode & mode)
	{
	  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
	  return -EBUSY;
	}
      devc->open_mode |= mode;
    }

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

  return 0;
}

static void
ich_audio_close (int dev, int mode)
{
  ich_portc *portc = audio_engines[dev]->portc;
  ich_devc *devc = audio_engines[dev]->devc;

  ich_audio_reset (dev);
  portc->open_mode = 0;

  if (devc->fifo_errors > 0)
    cmn_err (CE_CONT, "%d fifo errors were detected\n", devc->fifo_errors);

  if (portc->port_type != DF_SPDIF)
    devc->open_mode &= ~mode;
  portc->audio_enabled &= ~mode;
}

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

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

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

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

static void
ich_audio_trigger (int dev, int state)
{
  ich_devc *devc = audio_engines[dev]->devc;
  ich_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  if (portc->open_mode & OPEN_WRITE)
    {
      if (state & PCM_ENABLE_OUTPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      if (portc->port_type == DF_SPDIF)
		ich_OUTB (devc, 0x1d, CTL_BASE, 0x7b);	/* Kickstart */
	      else
		ich_OUTB (devc, 0x1d, CTL_BASE, 0x1b);	/* Kickstart */
	      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;
	      if (portc->port_type == DF_SPDIF)
		ich_OUTB (devc, 0x00, CTL_BASE, 0x7b);	/* reset */
	      else
		ich_OUTB (devc, 0x00, CTL_BASE, 0x1b);	/* reset */
	    }
	}
    }
  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      ich_OUTB (devc, 0x1d, CTL_BASE, 0x0b);	/* Kickstart */
	      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;
	      ich_OUTB (devc, 0x00, CTL_BASE, 0x0b);	/* reset */
	    }
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

/*ARGSUSED*/
static int
ich_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  ich_devc *devc = audio_engines[dev]->devc;
  ich_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  int i, n, speed;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  ich_OUTB (devc, 0x02, CTL_BASE, 0x0b);	/* Reset */
  ich_OUTL (devc, devc->recBDL_phys, CTL_BASE, 0x00);	/* BDL base */

  speed = portc->speed;
  speed = (speed * 240) / intelpci_rate_tuning;
  ac97_recrate (&devc->ac97devc, speed);

  n = bcount;
  if (n > BDL_SIZE)
    n = BDL_SIZE;

  for (i = 0; i < n; i++)
    {
      devc->recBDL[i].addr = dmap->dmabuf_phys + (i * dmap->fragment_size);

      /* SiS7012 uses bytes, ICH uses samples */
      if (devc->model == SIS_7012)
	devc->recBDL[i].size = (dmap->fragment_size);
      else
	devc->recBDL[i].size = (dmap->fragment_size / 2);

      devc->recBDL[i].flags = 0xc000;	/* IOC interrupts */
      devc->rec_frag_index[i] = i;
    }
  ich_OUTB (devc, n - 1, CTL_BASE, 0x05);	/* Set last valid descriptor */

  devc->rec_currbuf = n % BDL_SIZE;
  devc->rec_currfrag = n;
  if (devc->rec_currfrag >= dmap->nfrags)
    devc->rec_currfrag = 0;

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

/*ARGSUSED*/
static int
ich_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  ich_devc *devc = audio_engines[dev]->devc;
  ich_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_out;
  int i, n, speed;
  unsigned int tmp;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  /* We need to add ac3 pass through support */
  if (devc->model == SIS_7012)
    {
      if (portc->bits == AFMT_AC3)
	ich_OUTB (devc, 0, CTL_BASE, 0x4c);
      else
	ich_OUTB (devc, 1, CTL_BASE, 0x4c);
    }

  ac97_spdif_setup (devc->mixer_dev, portc->speed, portc->bits);

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

  /* do SPDIF out */
  if ((portc->port_type == DF_SPDIF) && (devc->model == NVIDIA_NFORCE2))
    {
      ich_OUTB (devc, 0x02, CTL_BASE, 0x7b);	/* Reset */
      ich_OUTL (devc, devc->spdifBDL_phys, CTL_BASE, 0x70);	/* BDL base */
      n = bcount;
      if (n > BDL_SIZE)
	n = BDL_SIZE;

      for (i = 0; i < n; i++)
	{
	  devc->spdifBDL[i].addr =
	    dmap->dmabuf_phys + (i * dmap->fragment_size);
	  devc->spdifBDL[i].size = (dmap->fragment_size / 2);
	  devc->spdifBDL[i].flags = 0xc000;	/* IOC interrupts */
	  devc->spdif_frag_index[i] = i;
	}

descriptor

      devc->spdif_currbuf = n % BDL_SIZE;
      devc->spdif_currfrag = n;
      if (devc->spdif_currfrag >= dmap->nfrags)
	devc->spdif_currfrag = 0;

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

      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

      return 0;
    }
  /* else do PCM */
  ich_OUTB (devc, 0x02, CTL_BASE, 0x1b);	/* Reset */
  ich_OUTL (devc, devc->playBDL_phys, CTL_BASE, 0x10);	/* BDL base */

  speed = portc->speed;
  speed = (speed * 240) / intelpci_rate_tuning;
  ac97_playrate (&devc->ac97devc, speed);

  /* Handle 4/6 channel output on 7012 */
  if (devc->model == SIS_7012)
    {
      tmp = ich_INL (devc, CTL_BASE, 0x30);

      /* set default to 2 channel mode */
      ich_OUTB (devc, ich_INB (devc, CTL_BASE, 0x2c) & 0x3f, CTL_BASE, 0x2c);

      if ((portc->channels == 4) && (tmp & (1 << 20)))
	ich_OUTB (devc, (ich_INB (devc, CTL_BASE, 0x2c) & 0x3f) | 0x40,
		  CTL_BASE, 0x2c);

      if ((portc->channels == 6) && (tmp & (1 << 21)))
	ich_OUTB (devc, (ich_INB (devc, CTL_BASE, 0x2c) & 0x3f) | 0x80,
		  CTL_BASE, 0x2c);
    }
  /* Handle 4/6 channel output on evrything other than ICH1 and SIS7012 */
  if ((devc->model != INTEL_ICH1) && (devc->model != SIS_7012))
    {
      tmp = ich_INL (devc, CTL_BASE, 0x30);

      /* set default to 2 channel mode */
      ich_OUTL (devc, ich_INL (devc, CTL_BASE, 0x2c) & 0x0cfffff, CTL_BASE,
		0x2c);

      if ((portc->channels == 4) && (tmp & (1 << 20)))
	ich_OUTL (devc,
		  (ich_INL (devc, CTL_BASE, 0x2c) & 0x00fffff) | 0x0100000,
		  CTL_BASE, 0x2c);

      if ((portc->channels == 6) && (tmp & (1 << 21)))
	ich_OUTL (devc,
		  (ich_INL (devc, CTL_BASE, 0x2c) & 0x00fffff) | 0x0200000,
		  CTL_BASE, 0x2c);
    }
  n = bcount;
  if (n > BDL_SIZE)
    n = BDL_SIZE;

  for (i = 0; i < n; i++)
    {
      devc->playBDL[i].addr = dmap->dmabuf_phys + (i * dmap->fragment_size);

      /* SiS7012 uses bytes, ICH uses samples */
      if (devc->model == SIS_7012)
	devc->playBDL[i].size = (dmap->fragment_size);
      else
	devc->playBDL[i].size = (dmap->fragment_size / 2);

      devc->playBDL[i].flags = 0xc000;	/* IOC interrupts */
      devc->play_frag_index[i] = i;
    }
  ich_OUTB (devc, n - 1, CTL_BASE, 0x15);	/* Set last valid descriptor */


  devc->play_currbuf = n % BDL_SIZE;
  devc->play_currfrag = n;
  if (devc->play_currfrag >= dmap->nfrags)
    devc->play_currfrag = 0;

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

static int
ich_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  ich_devc *devc = audio_engines[dev]->devc;
  ich_portc *portc = audio_engines[dev]->portc;
  int p = 0, f = 0, c = 0;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  if (direction == PCM_ENABLE_OUTPUT)
    {
      if (portc->port_type == DF_PCM)
	{
	  f = ich_INB (devc, CTL_BASE, 0x14);	/* Current buffer */

	  if (devc->model == SIS_7012)

buffer

	  else

buffer

	  c = devc->play_frag_index[f];	/* Current fragment */

	  if (devc->model == SIS_7012)
	    p = dmap->fragment_size - (p);	/* Remaining bytes */
	  else
	    p = dmap->fragment_size - (p * 2);	/* Remaining bytes */
	}
      if ((portc->port_type == DF_SPDIF) && (devc->model == NVIDIA_NFORCE2))
	{
	  f = ich_INB (devc, CTL_BASE, 0x74);	/* Current buffer */

buffer

	  c = devc->play_frag_index[f];	/* Current fragment */
	  p = dmap->fragment_size - (p * 2);	/* Remaining bytes */
	}
    }

Handle input

  if (direction == PCM_ENABLE_INPUT)
    {

      f = ich_INB (devc, CTL_BASE, 0x04);	/* Current buffer */

      if (devc->model == SIS_7012)

buffer

      else

buffer

      c = devc->rec_frag_index[f];	/* Current fragment */

      if (devc->model == SIS_7012)
	p = dmap->fragment_size - (p);	/* Remaining bytes */
      else
	p = dmap->fragment_size - (p * 2);	/* Remaining bytes */
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return p + c * dmap->fragment_size;
}

#if 0
static int
ich_calibrate_speed (int dev, int nominal_speed, int true_speed)
{
  unsigned int fix;
  DDB (smn_err (CE_CONT,
		"ich_calibrate_speed(%d, %d, %d)\n", dev, nominal_speed,
		true_speed));

  fix = ((240 * true_speed) + nominal_speed / 2) / nominal_speed;
  DDB (cmn_err (CE_NOTE, "intelpci_rate_tuning = %d\n", fix));
  if (fix > 1)
    intelpci_rate_tuning = fix;

  return 0;
}
#endif

static const audiodrv_t ich_audio_driver = {
  ich_audio_open,
  ich_audio_close,
  ich_audio_output_block,
  ich_audio_start_input,
  ich_audio_ioctl,
  ich_audio_prepare_for_input,
  ich_audio_prepare_for_output,
  ich_audio_reset,
  NULL,
  NULL,
  ich_audio_reset_input,
  ich_audio_reset_output,
  ich_audio_trigger,
  ich_audio_set_rate,
  ich_audio_set_format,
  ich_audio_set_channels,
  NULL,
  NULL,
  NULL,				/* ich_check_input, */
  NULL,				/* ich_check_output, */
  NULL,				/* ich_alloc_buffer, */
  NULL,				/* ich_free_buffer, */
  NULL,
  NULL,
  ich_get_buffer_pointer,
  NULL				/* ich_calibrate_speed */
};

static int
ich_init (ich_devc * devc)
{
  int my_mixer, adev, opts;
  int i, max_port;
  unsigned int reg;
  int first_dev = 0;
  oss_native_word phaddr;

  /* ACLink on, warm reset */
  reg = ich_INL (devc, CTL_BASE, 0x2c);
  if ((reg & 0x02) == 0)
    reg |= 2;
  else
    reg |= 4;
  reg &= ~8;
  ich_OUTL (devc, reg, CTL_BASE, 0x2c);
  oss_udelay (500);
  if (devc->model == SIS_7012)
    {
      reg |= 0x10;
      ich_OUTL (devc, reg, CTL_BASE, 0x2c);
      oss_udelay (500);
    }
  /* disable interrupts */
  ich_OUTB (devc, 0x00, CTL_BASE, 0x0b);
  ich_OUTB (devc, 0x00, CTL_BASE, 0x1b);
  ich_OUTB (devc, 0x00, CTL_BASE, 0x2b);
  ich_OUTB (devc, 0x00, CTL_BASE, 0x7b);

  devc->bdlBuffer =
    CONTIG_MALLOC (devc->osdev, 4 * 32 * 32, MEMLIMIT_32BITS, &phaddr);
  if (devc->bdlBuffer == NULL)
    {
      cmn_err (CE_WARN, "Failed to allocate BDL\n");
      return 0;
    }
  devc->playBDL = (bdl_t *) devc->bdlBuffer;
  devc->playBDL_phys = phaddr;
  devc->recBDL = (bdl_t *) (devc->bdlBuffer + (32 * 32));
  devc->recBDL_phys = phaddr + 32 * 32;
  devc->spdifBDL = (bdl_t *) (devc->bdlBuffer + (2 * 32 * 32));
  devc->spdifBDL_phys = phaddr + 2 * 32 * 32;


Init mixer

  my_mixer =
    ac97_install (&devc->ac97devc, "ICH AC97 Mixer", ac97_read, ac97_write,
		  devc, devc->osdev);
  if (my_mixer == -1)
    {
      cmn_err (CE_WARN, "AC97 mixer installation failed\n");
      return 0;			/* No mixer */
    }

  devc->mixer_dev = my_mixer;
  mixer_devs[my_mixer]->priority = 10;	/* Known motherboard device */

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

  /* enable variable rate mode */
  ac97_write (devc, 0x2a, ac97_read (devc, 0x2a) | 9);
  if (!(ac97_read (devc, 0x2a) & 1))
    DDB (cmn_err (CE_NOTE, "VRA not supported...using GRC\n"));

  /* Enable SPDIF for SiS 7012 */
  if (devc->model == SIS_7012)
    {
      ich_OUTL (devc, ich_INL (devc, CTL_BASE, 0x2c) | (1 << 10), CTL_BASE,
		0x2c);
      ich_OUTL (devc, ich_INL (devc, CTL_BASE, 0x4c) | 1, CTL_BASE, 0x4c);
    }
  if (devc->model == NVIDIA_NFORCE2)
    max_port = 3;
  else
    max_port = 2;

  for (i = 0; i < max_port; i++)
    {
      ich_portc *portc = &devc->portc[i];
      char tmp_name[100];
      int port_fmt = DF_PCM;
      int formats = AFMT_S16_LE | AFMT_AC3;
      strcpy (tmp_name, devc->chip_name);
      opts = ADEV_AUTOMODE | ADEV_16BITONLY | ADEV_STEREOONLY | ADEV_COLD;

      if (!ac97_varrate (&devc->ac97devc))
	{
	  opts |= ADEV_FIXEDRATE;
	}
      if (i == 0)
	{
	  strcpy (tmp_name, devc->chip_name);
	  opts |= ADEV_DUPLEX;
	}
      if (i == 1)
	{
	  strcpy (tmp_name, devc->chip_name);
	  opts |= ADEV_DUPLEX | ADEV_SHADOW;
	}
      if (i == 2)
	{
	  sprintf (tmp_name, "%s S/PDIF out", devc->chip_name);
	  opts |= ADEV_NOINPUT | ADEV_SPECIAL | ADEV_FIXEDRATE;
	  port_fmt = DF_SPDIF;
	  oss_audio_set_devname ("spdout");
	}
      if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					devc->osdev,
					devc->osdev,
					tmp_name,
					&ich_audio_driver,
					sizeof (audiodrv_t),
					opts, formats, NULL, -1)) < 0)
	{
	  adev = -1;
	  return 0;
	}
      else
	{
	  if (i == 0)
	    first_dev = adev;
	  audio_engines[adev]->devc = devc;
	  audio_engines[adev]->portc = portc;
	  audio_engines[adev]->rate_source = first_dev;
	  audio_engines[adev]->mixer_dev = my_mixer;
	  audio_engines[adev]->max_block = 64 * 1024;

	  /* fix a timeout bug with Nforce2 */
	  if ((devc->model == NVIDIA_NFORCE) ||
	      (devc->model == NVIDIA_NFORCE2))
	    {
	      audio_engines[adev]->min_block = 4096;
	      audio_engines[adev]->max_block = 4096;
	    }

	  audio_engines[adev]->min_rate =
	    (opts & ADEV_FIXEDRATE) ? 48000 : 5000;
	  audio_engines[adev]->max_rate = 48000;
	  audio_engines[adev]->caps |= PCM_CAP_FREERATE;
	  audio_engines[adev]->min_channels = 2;
	  audio_engines[adev]->max_channels = 6;
	  portc->open_mode = 0;
	  portc->audio_enabled = 0;
	  portc->audiodev = adev;
	  portc->port_type = port_fmt;
	  if (audio_engines[adev]->flags & ADEV_FIXEDRATE)
	    audio_engines[adev]->fixed_rate = 48000;
	}
    }
  return 1;
}

int
ich_attach (oss_device_t * osdev)
{
  unsigned char pci_irq_line, pci_revision /* , pci_latency */ ;
  unsigned short pci_command, vendor, device;
  unsigned int pci_ioaddr0, pci_ioaddr1;
  unsigned int dw;

  ich_devc *devc;

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

  devc->osdev = osdev;
  osdev->devc = devc;
  devc->open_mode = 0;

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

  DDB (cmn_err
       (CE_CONT, "ich_attach(Vendor %x, device %x)\n", vendor, device));

  if ((vendor != INTEL_VENDOR_ID && vendor != SIS_VENDOR_ID &&
       vendor != NVIDIA_VENDOR_ID && vendor != AMD_VENDOR_ID) ||
      (device != INTEL_DEVICE_ICH1 && device != INTEL_DEVICE_ICH1R1 &&
       device != INTEL_DEVICE_ICH1R2 && device != INTEL_DEVICE_ICH2 &&
       device != INTEL_DEVICE_ICH3 && device != INTEL_DEVICE_ICH4 &&
       device != INTEL_DEVICE_ICH5 && device != INTEL_DEVICE_ESB &&
       device != INTEL_DEVICE_ICH6 && device != INTEL_DEVICE_ICH7 &&
       device != SIS_DEVICE_7012 &&
       device != AMD_DEVICE_768 && device != AMD_DEVICE_8111 &&
       device != NVIDIA_DEVICE_NFORCE && device != NVIDIA_DEVICE_NFORCE2 &&
       device != NVIDIA_DEVICE_NFORCE3 && device != NVIDIA_DEVICE_CK8S &&
       device != NVIDIA_DEVICE_NFORCE4 && device != NVIDIA_DEVICE_CK8 &&
       device != NVIDIA_DEVICE_MCP51) && device != NVIDIA_DEVICE_MCP4)
    {
      cmn_err (CE_WARN, "Hardware not recognized (vendor=%x, dev=%x)\n",
	       vendor, device);
      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_ioaddr0);
  pci_read_config_dword (osdev, PCI_BASE_ADDRESS_1, &pci_ioaddr1);

  switch (device)
    {
    case INTEL_DEVICE_ICH1:
      devc->model = INTEL_ICH1;
      devc->chip_name = "Intel ICH (2415)";
      break;
    case INTEL_DEVICE_ICH1R1:
      devc->model = INTEL_ICH1;
      devc->chip_name = "Intel ICHR1(2425)";
      break;
    case INTEL_DEVICE_ICH1R2:
      devc->model = INTEL_ICH1;
      devc->chip_name = "Intel ICHR2 (7195)";
      break;
    case INTEL_DEVICE_ICH2:
      devc->model = INTEL_ICH1;
      devc->chip_name = "Intel ICH2 (2445)";
      break;
    case INTEL_DEVICE_ICH3:
      devc->model = INTEL_ICH3;
      devc->chip_name = "Intel ICH3 (2485)";
      break;
    case INTEL_DEVICE_ICH4:
      devc->model = INTEL_ICH4;
      devc->chip_name = "Intel ICH4 (24C5)";
      break;
    case INTEL_DEVICE_ICH5:
      devc->model = INTEL_ICH4;
      devc->chip_name = "Intel ICH5 (24D5)";
      break;
    case INTEL_DEVICE_ICH6:
      devc->model = INTEL_ICH4;
      devc->chip_name = "Intel ICH6 (266E)";
      break;
    case INTEL_DEVICE_ICH7:
      devc->model = INTEL_ICH4;
      devc->chip_name = "Intel ICH7 (27DE)";
      break;
    case INTEL_DEVICE_ESB:
      devc->model = INTEL_ICH4;
      devc->chip_name = "Intel ICH5 (25a6)";
      break;
    case SIS_DEVICE_7012:
      devc->model = SIS_7012;
      devc->chip_name = "SiS 7012";
      break;
    case NVIDIA_DEVICE_NFORCE:
      devc->model = NVIDIA_NFORCE;
      devc->chip_name = "Nvidia nForce";
      break;
    case NVIDIA_DEVICE_NFORCE2:
      devc->model = NVIDIA_NFORCE2;
      devc->chip_name = "Nvidia nForce2";
      pci_read_config_dword (osdev, 0x4c, &dw);
      dw |= 0x1000000;
      pci_write_config_dword (osdev, 0x4c, dw);
      break;
    case NVIDIA_DEVICE_NFORCE3:
      devc->model = NVIDIA_NFORCE2;
      devc->chip_name = "Nvidia nForce3";
      pci_read_config_dword (osdev, 0x4c, &dw);
      dw |= 0x1000000;
      pci_write_config_dword (osdev, 0x4c, dw);
      break;
    case NVIDIA_DEVICE_CK8S:
      devc->model = NVIDIA_NFORCE2;
      devc->chip_name = "Nvidia CK8S";
      pci_read_config_dword (osdev, 0x4c, &dw);
      dw |= 0x1000000;
      pci_write_config_dword (osdev, 0x4c, dw);
      break;
    case NVIDIA_DEVICE_NFORCE4:
      devc->model = NVIDIA_NFORCE2;
      devc->chip_name = "Nvidia nForce4";
      pci_read_config_dword (osdev, 0x4c, &dw);
      dw |= 0x1000000;
      pci_write_config_dword (osdev, 0x4c, dw);
      break;
    case NVIDIA_DEVICE_CK8:
      devc->model = NVIDIA_NFORCE2;
      devc->chip_name = "Nvidia CK8";
      pci_read_config_dword (osdev, 0x4c, &dw);
      dw |= 0x1000000;
      pci_write_config_dword (osdev, 0x4c, dw);
      break;
    case NVIDIA_DEVICE_MCP51:
      devc->model = NVIDIA_NFORCE2;
      devc->chip_name = "Nvidia MCP51";
      pci_read_config_dword (osdev, 0x4c, &dw);
      dw |= 0x1000000;
      pci_write_config_dword (osdev, 0x4c, dw);
      break;
    case NVIDIA_DEVICE_MCP4:
      devc->model = NVIDIA_NFORCE2;
      devc->chip_name = "Nvidia MCP4";
      pci_read_config_dword (osdev, 0x4c, &dw);
      dw |= 0x1000000;
      pci_write_config_dword (osdev, 0x4c, dw);
      break;
    case AMD_DEVICE_768:
      devc->model = AMD_768;
      devc->chip_name = "AMD 768";
      break;
    case AMD_DEVICE_8111:
      devc->model = AMD_8111;
      devc->chip_name = "AMD 8111";
      break;
    default:
      devc->chip_name = "Unknown ICH chip";
    }

  if (((pci_ioaddr1 == 0) || (intelpci_force_mmio)) &&
      (devc->model == INTEL_ICH4))
    {
      unsigned int ioaddr;

      /* read bar2 and bar3 for getting mmap address */
      pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_2, &ioaddr);
      devc->ac97_membar_addr = ioaddr;
      pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_3, &ioaddr);
      devc->membar_addr = ioaddr;

      /* get virtual address */
      devc->ac97_membar_virt =
	(char *) MAP_PCI_MEM (devc->osdev, 2, devc->ac97_membar_addr, 512);
      devc->membar_virt =
	(char *) MAP_PCI_MEM (devc->osdev, 3, devc->membar_addr, 256);
      devc->mem_mode = MMAP_MODE;
    }
  else
    devc->mem_mode = IO_MODE;

  if (devc->mem_mode == IO_MODE)
    {
      if (devc->model == INTEL_ICH4)
	{

enable the IOSE bit in 0x41 for legacy mode for ICH4/ICH5

	  pci_write_config_byte (osdev, 0x41, 1);

	  /* Set the secondary codec ID */
	  pci_write_config_byte (osdev, 0x40, 0x39);
	}
      /* Remove I/O space marker in bit 0. */
      devc->ac97_base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr0);
      devc->base = MAP_PCI_IOADDR (devc->osdev, 1, pci_ioaddr1);
      devc->ac97_base &= ~0xF;
      devc->base &= ~0xF;
    }
  pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO | PCI_COMMAND_MEMORY;
  pci_write_config_word (osdev, PCI_COMMAND, pci_command);

  devc->irq = pci_irq_line;

  if (devc->mem_mode != IO_MODE)
    {
      devc->base = devc->membar_addr;
    }

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

  oss_register_device (osdev, devc->chip_name);

  if (oss_register_interrupts (devc->osdev, 0, ichintr, NULL) < 0)
    {
      cmn_err (CE_WARN, "Unable to install interrupt handler\n");
      return 0;
    }

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

int
ich_detach (oss_device_t * osdev)
{
  ich_devc *devc = (ich_devc *) osdev->devc;

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

  /* disable interrupts */
  ich_OUTB (devc, 0x00, CTL_BASE, 0x0b);
  ich_OUTB (devc, 0x00, CTL_BASE, 0x1b);
  ich_OUTB (devc, 0x00, CTL_BASE, 0x2b);
  ich_OUTB (devc, 0x00, CTL_BASE, 0x7b);
  oss_unregister_interrupts (devc->osdev);

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

  if (devc->bdlBuffer)
    CONTIG_FREE (devc->osdev, devc->bdlBuffer, 4 * 32 * 32);
  devc->bdlBuffer = NULL;

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

  if ((devc->mem_mode == MMAP_MODE) && (devc->membar_addr != 0))
    {
      UNMAP_PCI_MEM (devc->osdev, 2, devc->ac97_membar_addr,
		     devc->ac97_membar_virt, 512);
      UNMAP_PCI_MEM (devc->osdev, 3, devc->membar_addr, devc->membar_virt,
		     256);
      devc->membar_addr = 0;
      devc->ac97_membar_addr = 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