/*
 * pas2_pcm.c Audio routines for PAS16
 */
/*
 * Copyright (C) by Hannu Savolainen 1993-1997
 *
 * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
 * Version 2 (June 1991). See the "COPYING" file distributed with this software
 * for more info.
 */
#include <linux/config.h>

#include "sound_config.h"

#if defined(CONFIG_PAS) && defined(CONFIG_AUDIO)

#ifndef DEB
#define DEB(WHAT)
#endif

#define PAS_PCM_INTRBITS (0x08)
/*
 * Sample buffer timer interrupt enable
 */

#define PCM_NON	0
#define PCM_DAC	1
#define PCM_ADC	2

static unsigned long pcm_speed = 0;	/* sampling rate */
static unsigned char pcm_channels = 1;	/* channels (1 or 2) */
static unsigned char pcm_bits = 8;	/* bits/sample (8 or 16) */
static unsigned char pcm_filter = 0;	/* filter FLAG */
static unsigned char pcm_mode = PCM_NON;
static unsigned long pcm_count = 0;
static unsigned short pcm_bitsok = 8;	/* mask of OK bits */
static int      pcm_busy = 0;
int             pas_audiodev = 0;
static int      open_mode = 0;

static int
pcm_set_speed (int arg)
{
  int             foo, tmp;
  unsigned long   flags;

  if (arg == 0)
    return pcm_speed;

  if (arg > 44100)
    arg = 44100;
  if (arg < 5000)
    arg = 5000;

  if (pcm_channels & 2)
    {
      foo = (596590 + (arg / 2)) / arg;
      arg = (596590 + (foo / 2)) / foo;
    }
  else
    {
      foo = (1193180 + (arg / 2)) / arg;
      arg = (1193180 + (foo / 2)) / foo;
    }

  pcm_speed = arg;

  tmp = pas_read (0x0B8A);

  /*
     * Set anti-aliasing filters according to sample rate. You really *NEED*
     * to enable this feature for all normal recording unless you want to
     * experiment with aliasing effects.
     * These filters apply to the selected "recording" source.
     * I (pfw) don't know the encoding of these 5 bits. The values shown
     * come from the SDK found on ftp.uwp.edu:/pub/msdos/proaudio/.
     *
     * I cleared bit 5 of these values, since that bit controls the master
     * mute flag. (Olav Wölfelschneider)
     *
   */
#if !defined NO_AUTO_FILTER_SET
  tmp &= 0xe0;
  if (pcm_speed >= 2 * 17897)
    tmp |= 0x01;
  else if (pcm_speed >= 2 * 15909)
    tmp |= 0x02;
  else if (pcm_speed >= 2 * 11931)
    tmp |= 0x09;
  else if (pcm_speed >= 2 * 8948)
    tmp |= 0x11;
  else if (pcm_speed >= 2 * 5965)
    tmp |= 0x19;
  else if (pcm_speed >= 2 * 2982)
    tmp |= 0x04;
  pcm_filter = tmp;
#endif

  save_flags (flags);
  cli ();

  pas_write (tmp & ~(0x40 | 0x80), 0x0B8A);
  pas_write (0x00 | 0x30 | 0x04, 0x138B);
  pas_write (foo & 0xff, 0x1388);
  pas_write ((foo >> 8) & 0xff, 0x1388);
  pas_write (tmp, 0x0B8A);

  restore_flags (flags);

  return pcm_speed;
}

static int
pcm_set_channels (int arg)
{

  if ((arg != 1) && (arg != 2))
    return pcm_channels;

  if (arg != pcm_channels)
    {
      pas_write (pas_read (0xF8A) ^ 0x20, 0xF8A);

      pcm_channels = arg;
      pcm_set_speed (pcm_speed);	/* The speed must be reinitialized */
    }

  return pcm_channels;
}

static int
pcm_set_bits (int arg)
{
  if (arg == 0)
    return pcm_bits;

  if ((arg & pcm_bitsok) != arg)
    return pcm_bits;

  if (arg != pcm_bits)
    {
      pas_write (pas_read (0x8389) ^ 0x04, 0x8389);

      pcm_bits = arg;
    }

  return pcm_bits;
}

static int
pas_audio_ioctl (int dev, unsigned int cmd, caddr_t arg)
{
  int             val;

  DEB (printk ("pas2_pcm.c: static int pas_audio_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd, arg));

  switch (cmd)
    {
    case SOUND_PCM_WRITE_RATE:
      val = *(int *) arg;
      return (*(int *) arg = pcm_set_speed (val));
      break;

    case SOUND_PCM_READ_RATE:
      return (*(int *) arg = pcm_speed);
      break;

    case SNDCTL_DSP_STEREO:
      val = *(int *) arg;
      return (*(int *) arg = pcm_set_channels (val + 1) - 1);
      break;

    case SOUND_PCM_WRITE_CHANNELS:
      val = *(int *) arg;
      return (*(int *) arg = pcm_set_channels (val));
      break;

    case SOUND_PCM_READ_CHANNELS:
      return (*(int *) arg = pcm_channels);
      break;

    case SNDCTL_DSP_SETFMT:
      val = *(int *) arg;
      return (*(int *) arg = pcm_set_bits (val));
      break;

    case SOUND_PCM_READ_BITS:
      return (*(int *) arg = pcm_bits);

    default:
      return -EINVAL;
    }

  return -EINVAL;
}

static void
pas_audio_reset (int dev)
{
  DEB (printk ("pas2_pcm.c: static void pas_audio_reset(void)\n"));

  pas_write (pas_read (0xF8A) & ~0x40, 0xF8A);	/* Disable PCM */
}

static int
pas_audio_open (int dev, int mode)
{
  int             err;
  unsigned long   flags;

  DEB (printk ("pas2_pcm.c: static int pas_audio_open(int mode = %X)\n", mode));

  save_flags (flags);
  cli ();
  if (pcm_busy)
    {
      restore_flags (flags);
      return -EBUSY;
    }

  pcm_busy = 1;
  restore_flags (flags);

  if ((err = pas_set_intr (PAS_PCM_INTRBITS)) < 0)
    return err;


  pcm_count = 0;
  open_mode = mode;

  return 0;
}

static void
pas_audio_close (int dev)
{
  unsigned long   flags;

  DEB (printk ("pas2_pcm.c: static void pas_audio_close(void)\n"));

  save_flags (flags);
  cli ();

  pas_audio_reset (dev);
  pas_remove_intr (PAS_PCM_INTRBITS);
  pcm_mode = PCM_NON;

  pcm_busy = 0;
  restore_flags (flags);
}

static void
pas_audio_output_block (int dev, unsigned long buf, int count,
			int intrflag)
{
  unsigned long   flags, cnt;

  DEB (printk ("pas2_pcm.c: static void pas_audio_output_block(char *buf = %P, int count = %X)\n", buf, count));

  cnt = count;
  if (audio_devs[dev]->dmap_out->dma > 3)
    cnt >>= 1;

  if (audio_devs[dev]->flags & DMA_AUTOMODE &&
      intrflag &&
      cnt == pcm_count)
    return;

  save_flags (flags);
  cli ();

  pas_write (pas_read (0xF8A) & ~0x40,
	     0xF8A);

  /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); */

  if (audio_devs[dev]->dmap_out->dma > 3)
    count >>= 1;

  if (count != pcm_count)
    {
      pas_write (pas_read (0x0B8A) & ~0x80, 0x0B8A);
      pas_write (0x40 | 0x30 | 0x04, 0x138B);
      pas_write (count & 0xff, 0x1389);
      pas_write ((count >> 8) & 0xff, 0x1389);
      pas_write (pas_read (0x0B8A) | 0x80, 0x0B8A);

      pcm_count = count;
    }
  pas_write (pas_read (0x0B8A) | 0x80 | 0x40, 0x0B8A);
#ifdef NO_TRIGGER
  pas_write (pas_read (0xF8A) | 0x40 | 0x10, 0xF8A);
#endif

  pcm_mode = PCM_DAC;

  restore_flags (flags);
}

static void
pas_audio_start_input (int dev, unsigned long buf, int count,
		       int intrflag)
{
  unsigned long   flags;
  int             cnt;

  DEB (printk ("pas2_pcm.c: static void pas_audio_start_input(char *buf = %P, int count = %X)\n", buf, count));

  cnt = count;
  if (audio_devs[dev]->dmap_out->dma > 3)
    cnt >>= 1;

  if (audio_devs[pas_audiodev]->flags & DMA_AUTOMODE &&
      intrflag &&
      cnt == pcm_count)
    return;

  save_flags (flags);
  cli ();

  /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); */

  if (audio_devs[dev]->dmap_out->dma > 3)
    count >>= 1;

  if (count != pcm_count)
    {
      pas_write (pas_read (0x0B8A) & ~0x80, 0x0B8A);
      pas_write (0x40 | 0x30 | 0x04, 0x138B);
      pas_write (count & 0xff, 0x1389);
      pas_write ((count >> 8) & 0xff, 0x1389);
      pas_write (pas_read (0x0B8A) | 0x80, 0x0B8A);

      pcm_count = count;
    }
  pas_write (pas_read (0x0B8A) | 0x80 | 0x40, 0x0B8A);
#ifdef NO_TRIGGER
  pas_write ((pas_read (0xF8A) | 0x40) & ~0x10, 0xF8A);
#endif

  pcm_mode = PCM_ADC;

  restore_flags (flags);
}

#ifndef NO_TRIGGER
static void
pas_audio_trigger (int dev, int state)
{
  unsigned long   flags;

  save_flags (flags);
  cli ();
  state &= open_mode;

  if (state & PCM_ENABLE_OUTPUT)
    pas_write (pas_read (0xF8A) | 0x40 | 0x10, 0xF8A);
  else if (state & PCM_ENABLE_INPUT)
    pas_write ((pas_read (0xF8A) | 0x40) & ~0x10, 0xF8A);
  else
    pas_write (pas_read (0xF8A) & ~0x40, 0xF8A);

  restore_flags (flags);
}
#endif

static int
pas_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  pas_audio_reset (dev);
  return 0;
}

static int
pas_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  pas_audio_reset (dev);
  return 0;
}

static struct audio_driver pas_audio_driver =
{
  pas_audio_open,
  pas_audio_close,
  pas_audio_output_block,
  pas_audio_start_input,
  pas_audio_ioctl,
  pas_audio_prepare_for_input,
  pas_audio_prepare_for_output,
  pas_audio_reset,
  NULL,
  NULL,
  NULL,
  NULL,
  pas_audio_trigger
};

void
pas_pcm_init (struct address_info *hw_config)
{
  DEB (printk ("pas2_pcm.c: long pas_pcm_init()\n"));

  pcm_bitsok = 8;
  if (pas_read (0xEF8B) & 0x08)
    pcm_bitsok |= 16;

  pcm_set_speed (DSP_DEFAULT_SPEED);

  if (num_audiodevs < MAX_AUDIO_DEV)
    {

      if ((pas_audiodev = sound_install_audiodrv (AUDIO_DRIVER_VERSION,
						  "Pro Audio Spectrum",
						  &pas_audio_driver,
					       sizeof (struct audio_driver),
						  DMA_AUTOMODE,
						  AFMT_U8 | AFMT_S16_LE,
						  NULL,
						  hw_config->dma,
						  hw_config->dma)) < 0)
	{
	  return;
	}
    }
  else
    printk ("PAS16: Too many PCM devices available\n");
}

void
pas_pcm_interrupt (unsigned char status, int cause)
{
  if (cause == 1)
    {
      /*
       * Halt the PCM first. Otherwise we don't have time to start a new
       * block before the PCM chip proceeds to the next sample
       */

      if (!(audio_devs[pas_audiodev]->flags & DMA_AUTOMODE))
	{
	  pas_write (pas_read (0xF8A) & ~0x40,
		     0xF8A);
	}

      switch (pcm_mode)
	{

	case PCM_DAC:
	  DMAbuf_outputintr (pas_audiodev, 1);
	  break;

	case PCM_ADC:
	  DMAbuf_inputintr (pas_audiodev);
	  break;

	default:
	  printk ("PAS: Unexpected PCM interrupt\n");
	}
    }
}

#endif
