/*
 * Purpose: Direct 24 bit multich driver for Envy24.
 *
 * This driver implements the all inputs and all outputs audio devices for
 * envy24. Unlike the ordinary driver this one doesn't use additional buffering.
 */
/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2008.
 *
 * This software is released under the BSD license.
 * See the COPYING file included in the main directory of this source
 * distribution for the license terms and conditions
 */


#include "oss_config.h"
#include "ac97.h"

#ifdef USE_LICENSING
#include "private/licensing/licensing.h"
#endif

#include "envy24.h"

#define OUTCH_NAMES "CH1/2 CH3/4 CH5/6 CH7/8 digital"
#define INCH_NAMES "CH1/2 CH3/4 CH5/6 CH7/8 digital monitor_mix"

/*
 * Audio routines
 */
extern int envy24_audio_set_rate (int dev, int arg);
int envy24d_get_buffer_pointer (int dev, dmap_t * dmap, int direction);

void
envy24d_playintr (envy24_devc * devc)
{
  oss_audio_outputintr (devc->direct_portc_out.audio_dev, 0);
}

void
envy24d_recintr (envy24_devc * devc)
{
  oss_audio_inputintr (devc->direct_portc_in.audio_dev, 0);
}

/*ARGSUSED*/ 
static short
envy24d_audio_set_channels (int dev, short arg)
{
  envy24d_portc *portc = audio_engines[dev]->portc;
  return portc->channels;
}

/*ARGSUSED*/ 
static unsigned int
envy24d_audio_set_format (int dev, unsigned int arg)
{
  return AFMT_S32_LE;
}

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

static void envy24d_audio_trigger (int dev, int state);

static void
envy24d_audio_reset (int dev)
{
  envy24d_audio_trigger (dev, 0);
}

/*ARGSUSED*/ 
static int
envy24d_audio_open (int dev, int mode, int open_flags)
{
  envy24_devc *devc = audio_engines[dev]->devc;
  envy24d_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags;

  if (!(mode & portc->direction))
    return OSS_EINVAL;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

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

  if (mode == OPEN_WRITE && (devc->play_channel_mask != 0))
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return OSS_EBUSY;
    }

  if (mode == OPEN_READ && (devc->rec_channel_mask != 0))
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return OSS_EBUSY;
    }

  portc->open_mode = mode;
  devc->direct_audio_opened |= mode;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

static void
envy24d_audio_close (int dev, int mode)
{
  envy24_devc *devc = audio_engines[dev]->devc;
  envy24d_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  portc->open_mode = 0;
  devc->direct_audio_opened &= ~mode;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

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

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

static void
envy24d_audio_trigger (int dev, int state)
{
  envy24_devc *devc = audio_engines[dev]->devc;
  envy24d_portc *portc = audio_engines[dev]->portc;
  int changed;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  changed = state ^ portc->trigger_bits;

  if (portc->direction == OPEN_WRITE && (changed & PCM_ENABLE_OUTPUT))
    {
      if (state & PCM_ENABLE_OUTPUT)
	{
#ifdef DO_TIMINGS
	  oss_do_timing ("Envy24d: Trigger start output");
#endif
	  portc->trigger_bits = state;
	  envy24_launch_play_engine (devc);
	}
      else
	{
#ifdef DO_TIMINGS
	  oss_do_timing ("Envy24d: Trigger stop output");
#endif
	  portc->trigger_bits = state;
	  envy24_stop_playback (devc);
	}
    }

  if (portc->direction == OPEN_READ && (changed & PCM_ENABLE_INPUT))
    {
      if (state & PCM_ENABLE_INPUT)
	{
#ifdef DO_TIMINGS
	  oss_do_timing ("Envy24d: Trigger start input");
#endif
	  portc->trigger_bits = state;
	  envy24_launch_recording (devc);
	}
      else
	{
#ifdef DO_TIMINGS
	  oss_do_timing ("Envy24d: Trigger stop input");
#endif
	  portc->trigger_bits = state;
	  envy24_stop_recording (devc);
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

/*ARGSUSED*/ 
static int
envy24d_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  envy24_devc *devc = audio_engines[dev]->devc;
  envy24_start_recording (devc);
  return 0;
}

/*ARGSUSED*/ 
static int
envy24d_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  envy24_devc *devc = audio_engines[dev]->devc;

  envy24_prepare_play_engine (devc);
  return 0;
}

static int
envy24d_alloc_buffer (int dev, dmap_t * dmap, int direction)
{
  envy24_devc *devc = audio_engines[dev]->devc;

  if (direction == OPEN_WRITE)
    {
      if (devc->playbuf == NULL)
	{
	  cmn_err (CE_CONT, "Envy24: No playback DMA buffer available\n");
	  return OSS_ENOSPC;
	}

      dmap->dmabuf = devc->playbuf;
      dmap->buffsize = devc->playbuffsize;
      dmap->dmabuf_phys = devc->playbuf_phys;
      return 0;
    }

  if (direction == OPEN_READ)
    {
      if (devc->recbuf == NULL)
	{
	  cmn_err (CE_CONT, "Envy24: No recording DMA buffer available\n");
	  return OSS_ENOSPC;
	}

      dmap->dmabuf = devc->recbuf;
      dmap->buffsize = devc->recbuffsize;
      dmap->dmabuf_phys = devc->recbuf_phys;
      return 0;
    }

  return OSS_EIO;
}

/*ARGSUSED*/ 
static int
envy24d_free_buffer (int dev, dmap_t * dmap, int direction)
{
  dmap->dmabuf = NULL;
  dmap->dmabuf_phys = 0;
  dmap->buffsize = 0;
  return 0;
}

int
envy24d_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{

  envy24_devc *devc = audio_engines[dev]->devc;
  int pos;

  if (direction == PCM_ENABLE_OUTPUT)
    {
      pos = INW (devc->osdev, devc->mt_base + 0x14);
      pos = (pos + 1) * 4;
      pos = dmap->bytes_in_use - pos;

      return pos;
    }

  if (direction == PCM_ENABLE_INPUT)
    {
      pos = INW (devc->osdev, devc->mt_base + 0x24);
      pos = (pos + 1) * 4;
      pos = dmap->bytes_in_use - pos;

      return pos;
    }

  return OSS_EIO;
}

/*ARGSUSED*/ 
static int
envy24d_check_input (int dev)
{
  cmn_err (CE_WARN, "Envy24d: Input timed out.\n");
  return OSS_EIO;
}

/*ARGSUSED*/ 
static int
envy24d_check_output (int dev)
{
  cmn_err (CE_WARN, "Envy24d: Output timed out (%d)\n", GET_JIFFIES ());
  return OSS_EIO;
}

static const audiodrv_t envy24d_audio_driver = {
  envy24d_audio_open,
  envy24d_audio_close,
  envy24d_audio_output_block,
  envy24d_audio_start_input,
  envy24d_audio_ioctl,
  envy24d_audio_prepare_for_input,
  envy24d_audio_prepare_for_output,
  envy24d_audio_reset,
  NULL,
  NULL,
  NULL,
  NULL,
  envy24d_audio_trigger,
  envy24_audio_set_rate,
  envy24d_audio_set_format,
  envy24d_audio_set_channels,
  NULL,
  NULL,
  envy24d_check_input,
  envy24d_check_output,
  envy24d_alloc_buffer,
  envy24d_free_buffer,
  NULL,
  NULL,
  envy24d_get_buffer_pointer
};

void
envy24d_install (envy24_devc * devc)
{

  int adev, out_dev, in_dev;
  char tmpname[128];
  envy24d_portc *portc;

  /*
   * Output device
   */
  sprintf (tmpname, "%s (all outputs)", devc->model_data->product);

  if ((out_dev = adev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION,
				    devc->osdev,
				    devc->osdev,
				    tmpname,
				    &envy24d_audio_driver,
				    sizeof (audiodrv_t),
				    ADEV_NOVIRTUAL | ADEV_NOINPUT | ADEV_COLD
				    | ADEV_SPECIAL | ADEV_32BITONLY,
				    AFMT_S32_LE, devc, -1,
				    "10ch_out")) >= 0)
    {
      portc = &devc->direct_portc_out;
      audio_engines[adev]->portc = portc;
      audio_engines[adev]->rate_source = devc->first_dev;
      audio_engines[adev]->caps |= DSP_CH_MULTI;
      audio_engines[adev]->min_channels = 10;
      audio_engines[adev]->max_channels = 10;
      audio_engines[adev]->outch_names = OUTCH_NAMES;
      audio_engines[adev]->min_block = devc->hw_pfragsize;
      audio_engines[adev]->max_block = devc->hw_pfragsize;
      audio_engines[adev]->min_rate = 8000;
      audio_engines[adev]->max_rate = 96000;

      audio_engines[adev]->mixer_dev = devc->mixer_dev;
      devc->direct_audio_opened = 0;
      portc->open_mode = 0;
      portc->audio_dev = adev;
      portc->channels = 10;
      portc->direction = OPEN_WRITE;
      audio_engines[adev]->port_number = 0;	/* First output channel */
    }

  /*
   * Input device
   */
  sprintf (tmpname, "%s (all inputs)", devc->model_data->product);

  if ((adev = in_dev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION,
				    devc->osdev,
				    devc->osdev,
				    tmpname,
				    &envy24d_audio_driver,
				    sizeof (audiodrv_t),
				    ADEV_NOVIRTUAL | ADEV_NOOUTPUT | ADEV_COLD
				    | ADEV_SPECIAL | ADEV_32BITONLY,
				    AFMT_S32_LE, devc, -1,
				    "12ch_in")) >= 0)
    {
      portc = &devc->direct_portc_in;
      audio_engines[adev]->portc = portc;
      audio_engines[adev]->rate_source = devc->first_dev;
      audio_engines[adev]->caps |= DSP_CH_MULTI;
      audio_engines[adev]->min_channels = 12;
      audio_engines[adev]->max_channels = 12;
      audio_engines[adev]->inch_names = INCH_NAMES;
      audio_engines[adev]->min_rate = 8000;
      audio_engines[adev]->max_rate = 96000;

      audio_engines[adev]->mixer_dev = devc->mixer_dev;
      devc->direct_audio_opened = 0;
      portc->open_mode = 0;
      portc->audio_dev = adev;
      audio_engines[adev]->min_block = devc->hw_pfragsize;
      audio_engines[adev]->max_block = devc->hw_pfragsize;
      portc->channels = 12;
      portc->direction = OPEN_READ;
      audio_engines[adev]->port_number = 10;	/* First input channel */
    }
}
