#define _PAS2_PCM_C_
/* linux/kernel/chr_drv/sound/pas2_pcm.c

The low level driver for the Pro Audio Spectrum ADC/DAC.

(C) 1992  Hannu Savolainen (hsavolai@cs.helsinki.fi)
	  Craig Metz (cmetz@thor.tjhsst.edu) */

#include "sound_config.h"

#ifdef CONFIGURE_SOUNDCARD

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/fcntl.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/tty.h>
#include <linux/ctype.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <sys/kd.h>
#include <linux/wait.h>
#include "sound_calls.h"

#include <linux/soundcard.h>
#include "pas.h"

#include "dev_table.h"

#if !defined(EXCLUDE_PAS) && !defined(EXCLUDE_AUDIO)

#define TRACE(WHAT)	/* (WHAT) */

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

#define DISABLE_INTR(flags)	__asm__ __volatile__("pushfl ; popl %0 ; cli":"=r" (flags));
#define ENABLE_INTR(flags)	__asm__ __volatile__("pushfl ; popl %0 ; sti":"=r" (flags));
#define RESTORE_INTR(flags)	__asm__ __volatile__("pushl %0 ; popfl": :"r" (flags));

#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/sample (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 my_devnum = 0;

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

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

	foo = 1193180 / arg;
	arg = 1193180 / foo;

	if (pcm_channels & 2) 
		foo = foo >> 1;

	pcm_speed = arg;	

	tmp = pas_read(FILTER_FREQUENCY);

	DISABLE_INTR(flags);

	pas_write(tmp & ~(F_F_PCM_RATE_COUNTER | F_F_PCM_BUFFER_COUNTER), FILTER_FREQUENCY);
	pas_write(S_C_C_SAMPLE_RATE | S_C_C_LSB_THEN_MSB | S_C_C_SQUARE_WAVE, SAMPLE_COUNTER_CONTROL);
	pas_write(foo & 0xff, SAMPLE_RATE_TIMER);
	pas_write((foo >> 8) & 0xff, SAMPLE_RATE_TIMER);
	pas_write(tmp, FILTER_FREQUENCY);

	RESTORE_INTR(flags);					

	return pcm_speed;
}

int pcm_set_channels(int arg)
{

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

	if (arg != pcm_channels) {
		pas_write(pas_read(PCM_CONTROL) ^ P_C_PCM_MONO, PCM_CONTROL);
				
		pcm_channels = arg;
		pcm_set_speed(pcm_speed);	/* The speed must be reinitialized */
	}

	return pcm_channels;
}

int pcm_set_bits(int arg)
{
	if ((arg & pcm_bitsok) != arg) 
		return pcm_bits;
		
	if (arg != pcm_bits) {
		pas_write(pas_read(SYSTEM_CONFIGURATION_2) ^ S_C_2_PCM_16_BIT, SYSTEM_CONFIGURATION_2);
				
		pcm_bits = arg;
	}

	return pcm_bits;
}

static int pas_pcm_ioctl(int dev, unsigned int cmd, unsigned int arg)
{
	TRACE(printk("pas2_pcm.c: static int pas_pcm_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd, arg));

	if ((cmd & (SOUND_BASE | SOUND_PCM)) == (SOUND_BASE | SOUND_PCM)) {
	switch (cmd & SOUND_FUNC) {
		case SOUND_RATE | SOUND_WRITE:
			return pcm_set_speed(arg);

		case SOUND_RATE | SOUND_READ:
			return pcm_speed;

		case SOUND_CHANNELS | SOUND_WRITE:
			return pcm_set_channels(arg);

		case SOUND_CHANNELS | SOUND_READ:
			return pcm_channels;

		case SOUND_BITS | SOUND_WRITE:
			return pcm_set_bits(arg);

		case SOUND_BITS | SOUND_READ:
			return pcm_bits;

		case SOUND_FILTER | SOUND_WRITE:	/* NOT YET IMPLEMENTED - WILL BE RSN */
			if (arg > 1)
				return -EINVAL;

			pcm_filter = arg;
		case SOUND_FILTER | SOUND_READ:
			return pcm_filter;
	}
	} else {
	switch (cmd) {
		case SNDCTL_DSP_SPEED:
			return pcm_set_speed(arg);
			break;

		case SNDCTL_DSP_STEREO:
			return pcm_set_channels(arg+1)-1;
			break;

		case SNDCTL_DSP_SAMPLESIZE:
			return pcm_set_bits(arg);
			break;

		default:
			return -EINVAL;
	}
	}

	return -EINVAL;
}

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

	pas_write(pas_read(PCM_CONTROL) & ~P_C_PCM_ENABLE, PCM_CONTROL);
}

static int pas_pcm_open(int dev, int mode)
{
	int err;

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

	if (mode != O_RDONLY && mode != O_WRONLY) {
		printk("PAS2: Attempt to open PCM device for simultaneous read and write");
		return -EINVAL;
	}

	if (dev != 0) {
		return -ENODEV;
	}

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

	if (!DMAbuf_open_dma(PAS_DMA)) {
		pas_remove_intr(PAS_PCM_INTRBITS);
		return -EBUSY;
	}

	pcm_set_bits(8);
	pcm_set_channels(1);
	pcm_set_speed(DSP_DEFAULT_SPEED);

	return 0;
}

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

	TRACE(printk("pas2_pcm.c: static void pas_pcm_close(void)\n"));

	DISABLE_INTR(flags);

	pas_pcm_reset(dev);
	DMAbuf_close_dma(PAS_DMA);
	pas_remove_intr(PAS_PCM_INTRBITS);
	pcm_mode = PCM_NON;

	RESTORE_INTR(flags);
}

static void pas_pcm_output_block(int dev, char *buf, int count)
{
	unsigned long flags;

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

	DISABLE_INTR(flags);

	count--;

	DMAbuf_start_dma(PAS_DMA, (long)buf, count, DMA_WRITE);

	if (PAS_DMA > 3)
		count >>= 1;

	if (count != pcm_count) {
		pas_write(pas_read(FILTER_FREQUENCY) & ~F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY);
		pas_write(S_C_C_SAMPLE_BUFFER | S_C_C_LSB_THEN_MSB | S_C_C_SQUARE_WAVE, SAMPLE_COUNTER_CONTROL);
		pas_write(count & 0xff, SAMPLE_BUFFER_COUNTER);
		pas_write((count >> 8) & 0xff, SAMPLE_BUFFER_COUNTER);
		pas_write(pas_read(FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY);

		pcm_count = count;
	}
	pas_write(pas_read(FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER | F_F_PCM_RATE_COUNTER, FILTER_FREQUENCY);
	pas_write(pas_read(PCM_CONTROL) | P_C_PCM_ENABLE | P_C_PCM_DAC_MODE, PCM_CONTROL);

	pcm_mode = PCM_DAC;

	RESTORE_INTR(flags);
}

static void pas_pcm_start_input(int dev, char *buf, int count)
{
	long addr;
	unsigned long flags;

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

	addr = (long)buf;

	DISABLE_INTR(flags);

	count--;

	DMAbuf_start_dma(PAS_DMA, addr, count, DMA_READ);
	
	if (PAS_DMA > 3)
		count >>= 1;

	if (count != pcm_count) {
		pas_write(pas_read(FILTER_FREQUENCY) & ~F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY);
		pas_write(S_C_C_SAMPLE_BUFFER | S_C_C_LSB_THEN_MSB | S_C_C_SQUARE_WAVE, SAMPLE_COUNTER_CONTROL);
		pas_write(count & 0xff, SAMPLE_BUFFER_COUNTER);
		pas_write((count >> 8) & 0xff, SAMPLE_BUFFER_COUNTER);
		pas_write(pas_read(FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY);

		pcm_count = count;
	}
	pas_write(pas_read(FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER | F_F_PCM_RATE_COUNTER, FILTER_FREQUENCY);
	pas_write( (pas_read(PCM_CONTROL) | P_C_PCM_ENABLE) & ~P_C_PCM_DAC_MODE, PCM_CONTROL);

	pcm_mode = PCM_ADC;

	RESTORE_INTR(flags);
}

static int pas_pcm_prepare_for_input(void) { return 0; }
static int pas_pcm_prepare_for_output(void) { return 0; }

static struct audio_operations pas_pcm_operations =
{
	pas_pcm_open,				/* */	
	pas_pcm_close,				/* */
	pas_pcm_output_block,			/* */
	pas_pcm_start_input,			/* */
	pas_pcm_ioctl,				/* */
	pas_pcm_prepare_for_input,		/* */
	pas_pcm_prepare_for_output,		/* */
	pas_pcm_reset				/* */
};

long pas_pcm_init(long mem_start)
{
	TRACE(printk("pas2_pcm.c: long pas_pcm_init(long mem_start = %X)\n", mem_start));

	pcm_bitsok = 8;
	if (pas_read(OPERATION_MODE_1) & O_M_1_PCM_TYPE) 
		pcm_bitsok |= 16;

	pcm_set_speed(DSP_DEFAULT_SPEED);

	dsp_devs[my_devnum=num_dspdevs++] = &pas_pcm_operations;

	return mem_start;
}

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

      pas_write (pas_read (PCM_CONTROL) & ~P_C_PCM_ENABLE,
		 PCM_CONTROL);

      switch (pcm_mode)
	{

	case PCM_DAC:
	  DMAbuf_outputintr (my_devnum);
	  break;

	case PCM_ADC:
	  DMAbuf_inputintr (my_devnum);
	  break;

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

#endif

#endif
