/*
 * sound/wavnc.c
 *
 * The low level driver for the RWA010 Rockwell Wave Artist codec chip
 * used in the Corel Computer NetWinder.
 *
 */

/*
 * Copyright (C) by Corel Computer 1998
 *
 * RWA010 specs received under NDA from Rockwell
 *
 * 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>


#define DEB(x)
#define DDB(x)
#define DEB1(x)
#include "sound_config.h"

#ifdef CONFIG_WA_VNC

#include "wa_vnc.h"

#define VERSION "0.82"
#define	WA_TIMER_PERIOD HZ/4	//check slider 4 times/sec

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

#define	MIXER_PRIVATE3_RESET	0x53570000
#define	MIXER_PRIVATE3_READ	0x53570001
#define	MIXER_PRIVATE3_WRITE	0x53570002

#define	VNC_INTERNAL_SPKR	0x01	//the sw mute on/off control bit
#define	VNC_INTERNAL_MIC	0x10	//the hw internal/handset mic bit
#define	VNC_LINE_OUT_MUTE	0x40	//the sw mute of line-out bit
#define	VNC_HANDSET_SW_ONLY	0x80	//software in total control of handset!

// use RECSRC = speaker to mark the internal microphone
//Some cheating involved here: there is no way to relay to the system, which
//microphone in in use (left = handset, or right = internal)
//So while I do not flag SPEAKER in the Recording Devices Mask, when on internal
// mike - I set the speaker bit hi. Some mixers can be confused a bit...

#define POSSIBLE_RECORDING_DEVICES	(SOUND_MASK_LINE |\
					 SOUND_MASK_MIC |\
					 SOUND_MASK_LINE1)//Line1 = analog phone

#define SUPPORTED_MIXER_DEVICES		(SOUND_MASK_SYNTH |\
					 SOUND_MASK_PCM |\
					 SOUND_MASK_MIC |\
					 SOUND_MASK_LINE |\
					 SOUND_MASK_LINE1 |\
					 SOUND_MASK_RECLEV |\
					 SOUND_MASK_IMIX |\
					 SOUND_MASK_VOLUME)

extern int getLED(void);
extern int setLED(int leds, int programID);
extern int set_CPLD(unsigned int bitset, unsigned int bitmask); 

typedef struct
  {
    int             base;
    int             irq;
    int             dma1, dma2;
    int             dual_dma;	/* 1, when two DMA channels allocated */
    int             debug_flag;

    int             audio_flags;
    int             record_dev, playback_dev;

    int             xfer_count;
    int             audio_mode;
    int             open_mode;
    int             intr_active;
    char           *chip_name, *name;
    int            *wa_osp;

    /* Mixer parameters */
    int             recmask;		//currently enabled recording device!
    int             supported_devices;	//SUPPORTED_MIXER_DEVICES
    int		    rec_devices;	//POSSIBLE_RECORDING_DEVICES
    int             dev_no;
    int             irq_ok;

    int		    handset_state;
    int		    mute_state;
    int		    soft_mute_flag;	//1 - handset mic/spkr + mute in sw
    int		    soft_volume_flag;	//1 - volume controlled by mixer prog 
    int		    hw_volume;		//remember old setting of hw vol
    int		    triggerflag;	//bit 0=write trigger,bit 1=read trigger
  }
wavnc_info;

static unsigned short levels[SOUND_MIXER_NRDEVICES] =
{
  0x5555,			/* Master Volume */
  0x0000,			/* Bass */
  0x0000,			/* Treble */
  0x5555,			/* FM */
  0x4b4b,			/* PCM */
  0x0000,			/* PC Speaker */
  0x0000,			/* Ext Line */
  0x0000,			/* Mic */
  0x0000,			/* CD */
  0x0000,			/* Recording monitor */
  0x0000,			/* SB PCM */
  0x0000,			/* Recording level */
  0x0000,			/* Input gain */
  0x0000,			/* Output gain */
  0x0000,			/* Aux1 */
  0x0000,			/* Aux2 */
  0x0000			/* Aux3 */
};

typedef struct wavnc_port_info
  {
    int             open_mode;
    int             speed;
    int             channels;
    int             audio_format;
  }
wavnc_port_info;

static int      nr_wavnc_devs = 0;

static wavnc_info adev_info;
static	struct	timer_list	wa_timer;

static int      wavnc_open (int dev, int mode);
static void     wavnc_close (int dev);
static int      wavnc_ioctl (int dev, unsigned int cmd, caddr_t arg);
static void     wavnc_output_block (int dev, unsigned long buf, int count, int intrflag);
static void     wavnc_start_input (int dev, unsigned long buf, int count, int intrflag);
static int      wavnc_prepare_for_output (int dev, int bsize, int bcount);
static int      wavnc_prepare_for_input (int dev, int bsize, int bcount);
static void     wavnc_halt (int dev);
static void     wavnc_halt_input (int dev);
static void     wavnc_halt_output (int dev);
static void     wavnc_trigger (int dev, int bits);

void waintr (int irq, void *dev_id, struct pt_regs *dummy);
int vnc_slider(wavnc_info * devc);
static void wa_slider(unsigned long data);
int wa_sendcmd (unsigned int cmd);
int wa_writecmd (unsigned int cmd, unsigned int arg);

void	delay10ms(void);
static int mixer_output (int right_vol, int left_vol, int div, int bits,
	      int mixer1, int mixer2, int shift);
static int wa_mixer_set (wavnc_info * devc, int whichDev, unsigned int level);
static void wavnc_mixer_reset (wavnc_info * devc);
void mute_mono(wavnc_info * devc, int mute);
void mute_lout(wavnc_info * devc, int mute);
void wa_mute(wavnc_info * devc, int mute);

int	reverse = 0;		//can be set from cmd line to revers slider

void	delay10ms(void)
{
	//wait 10 ms
	current->state = TASK_INTERRUPTIBLE;
	current->timeout = jiffies + 1;	//wait 10 ms
	schedule();
}



static void wa_slider(unsigned long data)
{
	if (vnc_slider(&adev_info))
	{
		wa_timer.expires = jiffies + 5;	//mixer reported change
	}
	else
	{
		wa_timer.expires = jiffies + WA_TIMER_PERIOD;
	}
	add_timer(&wa_timer);

}


int
wa_sendcmd (unsigned int cmd)
{
int count;

    DEB1(printk("wa_sendcmd: cmd=0x%04X...", cmd));
    
    tenmicrosec(adev_info.wa_osp);

    if(inb(STATR) & CMD_RF)
    {
	count = inw(CMDR);	//flush the port

	printk("", count);	//useless, but produces a needed delay...

	DEB1(printk("\nwa_sendcmd: flushed old response: %04X.\n",count));

    }

    count = 5000;	//preset timeout at 5000 loops....

    while(!(inb(STATR) & CMD_WE) && count--)   {};	//wait till bit HI
    
    if(count)	//ready BEFORE timeout?
    {
        outw(cmd, CMDR);	//output the command
	DEB1(printk(" Done OK.\n"));
	tenmicrosec(adev_info.wa_osp);

	return 1;
    }
    else
    {
	DEB1(printk(" Error!\n"));
	return 0;
	}
}

int
wa_writecmd (unsigned int cmd, unsigned int arg)
{
int count;
    
    DEB1(printk("wa_writecmd: cmd=0x%04X, arg=0x%04X ...", cmd, arg));

    tenmicrosec(adev_info.wa_osp);

    if(inb(STATR) & CMD_RF)
    {
	count = inw(CMDR);	//flush the port
	DEB1(printk("\nwa_writecmd: flushed %04X response.\n",count));
    }
    
    count = 5000;	//preset timeout at 5000 loops....

    while(!(inb(STATR) & CMD_WE) && count--)   {};	//wait till bit HI
    
    if(count)	//ready BEFORE timeout?
    {
	//start with writing the command word
        outw(cmd, CMDR);	//output the command
        count = 5000;	//preset timeout at 5000 loops....
    	while(!(inb(STATR) & CMD_WE) && count--)   {};	//wait till bit HI
	if(!count)
	{
	
		DEB1(printk(" Timeout 1!.\n"));
		return(0);
	}
	else
	{
	    //write the parameter
	    outw(arg,CMDR);
	    
	    tenmicrosec(adev_info.wa_osp);	//Rockwell answers in 5 us max.

    	    if(!(inb(STATR) & CMD_RF))
		    tenmicrosec(adev_info.wa_osp);

	    count = inw(CMDR);

	    DEB1(printk("Done=%04X.\n",count));
	    if (!count)
		    tenmicrosec(adev_info.wa_osp);
	    return(count);	//0 = error, 1 = OK
	}
    }
    else
    {
    	DEB1(printk(" Timeout 2!.\n"));

	return 0;
	}
}



//-----------------------------------------------------------------------
//This function is suitable to update settings in all "symmetrical" registers,
//namely 1..8. Registers 9 and 10 are not adhering to the left-right concept...
//Parameters: right volume, left volume, scale of volume (0..max),
//		 bitmask in the register, left reg, right reg, the LSB of mask

static int
mixer_output (int right_vol, int left_vol, int div, int bits,
	      int mixer1, int mixer2, int shift)/* Mixer registers to touch */
{
  int             left = left_vol * div / 100;
  int             right = right_vol * div / 100;

  int	left1 = 0;
  int	right1 = 0;

  int	left2 = 0;
  int	right2 = 0;

	left1 = (mixer1-1)<<8;		// get ready for command nn30H
	right1= (mixer2-1)<<8;

//first read current values
	wa_sendcmd(left1+0x30);
	while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready...
	left2 = inw(CMDR);

	wa_sendcmd(right1+0x30);
	while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready...
	right2 = inw(CMDR);

	DDB(printk("wa_mixer: Current left=%04X, right=%04X.\n",left2,right2));

//now that both current values are in - update bits...
	if (shift) left <<= shift;
	left2 &= ~bits;
	left2 |= left;

	if (shift) right <<= shift;
	right2 &= ~bits;
	right2 |= right;

//and finally - write the reg pair back....
	
	wa_sendcmd(0x32);
	wa_sendcmd(left2);
	wa_sendcmd(right2);

  return (left_vol | (right_vol << 8));
}


static int
wa_mixer_set (wavnc_info * devc, int whichDev, unsigned int level)
{
  unsigned int	left, right, devmask, changed, i, mixer = 0;
  unsigned int	left1, right1;

  left = level & 0x7f;
  right = (level & 0x7f00) >> 8;

  if (whichDev < SOUND_MIXER_NRDEVICES)
  {
    if ((1 << whichDev) & devc->rec_devices)
    {
      mixer = 0x20;
    }
    else
    {
      mixer = 0x00;
    }
  }

  DDB (printk ("wa_mixer_set(dev = %d, level = %X, mixer = %X.)\n", whichDev, level, mixer));

  switch (whichDev)
    {
//  We have 3 bits on the Left/Right Mixer Gain, bits 3,2,1 on 3 and 7
    case SOUND_MIXER_VOLUME:	/* Master volume (0-7) */
      levels[whichDev] = mixer_output (right, left, 7, 0x000E, 3, 7, 1);
      break;

    case SOUND_MIXER_MIC:	/* Mono microphone (0-3) mute,0db,10db,20db */

//this control is usable only if mic is hooked to a mixer, instead ADC
//	levels[whichDev] = mixer_output (right,left,31,0x07C0,3,7,6);

	levels[whichDev] = mixer_output (right,left,3,0x0030,3,7,4);
/*
//we do not need to mute volume of an unused mic - it is simply unused...
	if (devc->handset_state & VNC_INTERNAL_MIC)// handset not plugged in?
		levels[whichDev] = mixer_output (right,0,3,0x0030,3,7,4);
	else
		levels[whichDev] = mixer_output (0,left,3,0x0030,3,7,4);
*/
      break;

// line and analog phone volumes are fake - present to enable selecting it
// as recoreding devices. The real volume is controlled by RECLEV
//  use LOUT/ROUT bits 10...6, reg 1 and 5
    case SOUND_MIXER_LINE:	/* External line (0-31) */
//      levels[whichDev] = mixer_output (right,left,31,0x07C0,1,5,6);
	levels[whichDev] = level;
      break;


//  use LINE1 bits 5...1, reg 1 and 5
    case SOUND_MIXER_LINE1:	/* Mono External Aux1 (0-31) */
//      levels[whichDev] = mixer_output (right,left,31,0x003E,1,5,1);
	levels[whichDev] = level;
      break;


    case SOUND_MIXER_RECLEV:	/* Recording level (0-7) */
//      levels[whichDev] = mixer_output (right,left,7,0x0007,4,8,0);

//	actually - convert it into adjustment of 16 levels by use of
//	the RX_Filter_Gain bit...

      levels[whichDev] = mixer_output (right,left,15,0x000F,4,8,0);
      break;


    case SOUND_MIXER_PCM:	/* WA PCM (0-7FFFH) */
	left1 = left * 0x7FFF /100;
	right1= right* 0x7FFF /100;

	wa_sendcmd(0x0031);	//change left and right PCM
	wa_sendcmd(left1);
	wa_sendcmd(right1);

	//we cannot store full 15 bits in 2*8 bits - so store only upper bytes
      levels[whichDev] = left | (right <<8);
      break;


    case SOUND_MIXER_SYNTH:	/* Internal synthesizer (0-31) */
	left1 = left * 0x7FFF /100;
	right1= right* 0x7FFF /100;

	wa_sendcmd(0x0131);	//change left and right FM
	wa_sendcmd(left1);
	wa_sendcmd(right1);

	//we cannot store full 15 bits in 2*8 bits - so store only upper bytes
      levels[whichDev] = left | (right <<8);
      break;


    case SOUND_MIXER_RECSRC:
      devmask = level & POSSIBLE_RECORDING_DEVICES;

      changed = devmask ^ devc->recmask;
      devc->recmask = devmask;

	DDB(printk("RECSRC: recmask: 0x%02X, changed: 0x%02X.\n",devmask,changed));

      for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
	if (changed & (1 << i))
	{
	    wa_mixer_set (devc, i, levels[i]);
	}


//new mixer programming - switch recording source using R/L_ADC_Mux_Select
//we are playing with left/right mux bit fields in reg 9.

// can not switch Mux_Select while recording!!!
// so for microphones - enable both left and right, play with levels only!
// unfortunately, we need to select the src of mono recording (left or right)
// before starting the recording - so can not dynamically switch between
// handset amd internal microphones...

	wa_sendcmd(0x0830);	//get current reg 9
	while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready...
	left1 = inw(CMDR);

	wa_sendcmd(0x0930);	// and reg 10 - just to write it back...
	while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready...
	right1 = inw(CMDR);

	DDB(printk("RECSRC: old left: 0x%04X, old right: 0x%04X.\n",
		left1&0x07, (left1>>3)&0x07));

	left1 &= ~0x03F;	//kill current left/right mux input select


	if (devmask & SOUND_MASK_MIC)	// handset or internal mic
	{
//		wa_sendcmd(0x16);	//PAUSE the ADC - no desired effect

		if (devc->handset_state & VNC_INTERNAL_MIC)// handset not plugged in?
		{
			wa_sendcmd(0x0134);	//set mono recording from right mic
//			left1 |= 0x0028;	//right=mic, left=none
//			devc->rec_devices |= SOUND_MASK_SPEAKER; //pretend int mic
		}
		else
		{
			wa_sendcmd(0x0034);	//set mono rec from left mic
//			left1 |= 0x0005;	// right=none, left=mic
//			devc->rec_devices &= ~SOUND_MASK_SPEAKER; //show no int mic
		}
		left1 |= 0x002D;	// right=mic, left=mic

//		wa_sendcmd(0x18);	//RESUME the ADC
	}

	else if (devmask & SOUND_MASK_LINE)
	{
		wa_sendcmd(0x0034);	//set mono rec from left (default)
		left1 |= 0x0012;	// right=Line, left=Line;
	}

	else if (devmask & SOUND_MASK_LINE1)
	{
		wa_sendcmd(0x0034);	//set mono rec from left aux1
		left1 |= 0x0004;	// right=none, left=Aux1;
	}

	DDB(printk("RECSRC level %X, left=0x%04X, right=0x%04X.\n",level,
		left1&0x07, (left1>>3)&0x07));

	//and finally - write the reg pair back....
	
	wa_sendcmd(0x32);
	wa_sendcmd(left1);
	wa_sendcmd(right1);


	//re-kick the recmon, if monitoring is on...
	if (levels[9])
	    wa_mixer_set (devc, 9, levels[9]);


      return devc->recmask;	//do not save in "levels",return current setting
      break;


    case SOUND_MIXER_IMIX:	/* Recording monitor */

//this part is good, if input connected to a mixer...
//so can be used for record-only modes...

	wa_sendcmd(0x0330);	//get current reg 4
	while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready...
	left1 = inw(CMDR);

	wa_sendcmd(0x0730);	// and reg 8
	while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready...
	right1 = inw(CMDR);

	DDB(printk("IMIX: old left: 0x%04X, old right: 0x%04X, recmask=%X.\n",left1, right1,devc->recmask));
	left1 &= ~0x03F0;	//kill current left/right mux input select
	right1 &= ~0x03F0;	// except for the DSP-out (Tx-filter = bit 10)

	if (devc->recmask & SOUND_MASK_MIC)	// handset or internal mic
	{
		if (devc->handset_state & VNC_INTERNAL_MIC)// handset not plugged in?
		{
			left1  |= 0x0D00;	// == cross-channel mic; 
			right1 |= 0x0C80;	// == right mic is internal mic;
		}
		else
		{
			left1  |= 0x0C80;	// == left mic is handset mic 
			right1 |= 0x0D00;	// == cross-channel mic;
		}
		mixer_output (right,left,31,0x07C0,3,7,6);
	}

	if (devc->recmask & SOUND_MASK_LINE1)
	{
		left1 |= 0x0C40;	// ==  Aux1;
		right1 |= 0x0C00;	// ==  none;
		mixer_output (right,left,31,0x003E,1,5,1);
	}

	if (devc->recmask & SOUND_MASK_LINE)
	{
		left1 |= 0x0C10;	// ==  Line;
		right1 |= 0x0C10;	// ==  Line;
		mixer_output (right,left,31,0x07C0,1,5,6);
	}

	DDB(printk("IMIX %X: left(4) 0x%04X, right(8) 0x%04X.\n",level,left1,right1));
	//and finally - write the reg pair back....
	
	wa_sendcmd(0x32);
	wa_sendcmd(left1);
	wa_sendcmd(right1);

	levels[whichDev] = level;

      break;







    default:
      return -(EINVAL);
    }

  return (levels[whichDev]);
}




int vnc_slider(wavnc_info * devc)
{
unsigned int volume, temp, hw_vol;
long unsigned flags;
unsigned int start_time;


#if 0
    //experimentaly add here checking of the orange "reset" button
    //if detected - change LED to orange...
    //this space is used, to avoid installing of a separate timer callback...
    printk("%02X%02X ",cShadow338,inb(0x338));
    temp = inb(0x338);
    if (temp & 0x01)	//if pressed    
    {
	printk("Orange pressed, cShadow338 = 0x%02X...\n",cShadow338);
    
	if (!(cShadow338 & 0x80))	//red LED not on yet?
	{
	    printk("Orange 1 pressed, calling setLED 3...\n");
	    setLED(3,(int)&vnc_slider);
	    printk("setLED done, cShadow338 = 0x%02X, getLED = %X...\n",cShadow338,getLED());
	    
	}
	else
	{
	    printk("Orange 2 pressed, calling setLED 1...\n");
	    setLED(1,(int)&vnc_slider);
	    printk("setLED done, cShadow338 = 0x%02X, getLED = %X...\n",cShadow338,getLED());
	    
	}
    }
#endif
    
/*
// joystick stuff - called from a timer callback...
*/
	//read the "buttons" state. Bit 0 = handset present, bit 1 = offhook
	// the state is "querable" via a private2 IOCTL call
	temp = inb(0x201) & 0x30;


//if NOT under exclusive sw control, any change in handset plugged in/out?
	if ((!(devc->soft_mute_flag & VNC_HANDSET_SW_ONLY)) && 
	    ((temp&VNC_INTERNAL_MIC) != (devc->handset_state&VNC_INTERNAL_MIC)))
	{
//DEB(printk("wa_mixer: handset: old = %02X, new = %02X.\n",devc->handset_state, temp));
		devc->handset_state = temp;
//DEB(printk("wa_mixer: soft_mute: old = %02X, new = 0.\n",devc->soft_mute_flag));
		devc->soft_mute_flag = 0x00;

		//handset on (bit=0)? enable handset mic, disable internal mic
		//only if current setting is for the other mic (not the line...)
		// actually changed: switch to apriopriate microphone
		if (!(temp&VNC_INTERNAL_MIC))
		{
			mute_mono(devc,1);	//cut - off speaker
			mute_lout(devc,1);	//cut off the line-out as well
/*
			// copy right mic volume to left mic volume
			temp = (levels[SOUND_MIXER_MIC])>>8;
			temp &= 0x7F;
			levels[SOUND_MIXER_MIC] = mixer_output (0,temp,15,0x000F,4,8,0);
*/
		}
		else
		{
			//speaker on, internal mic on
			mute_mono(devc,0);
			mute_lout(devc,0);	//reenable line-out as well
/*
			// copy left mic volume to right mic volume
			temp = (levels[SOUND_MIXER_MIC])&0x7F;
			levels[SOUND_MIXER_MIC] = mixer_output (temp,0,15,0x000F,4,8,0);
*/
		}

		if (devc->recmask & SOUND_MASK_MIC)	// handset or internal mic
		{
		    wa_mixer_set(devc, SOUND_MIXER_RECSRC,
			devc->recmask);
		}
	}


	volume = 0xFF;

	save_flags(flags);
	cli();

	outb(0x1FF, 0x201);	//fire joystick timer
	start_time = *(volatile unsigned int*)(0xE1000304);
	while (volume && (inb(0x201) & 0x01))
	{
		volume--;
	}

	hw_vol = *(volatile unsigned int*)(0xE1000304);
	restore_flags(flags);

	hw_vol = start_time - hw_vol;

	// occasionally we get a hw timer wrap condition in which case the
	// hw_vol is negative. In such case just wait for a next callback...
	// Also, if joystick bit does not respond - ignore volume setting...

	if ((hw_vol & 0x80000000) || !volume)
		return(0);	//no change

	volume = hw_vol;	//save it for debug display

	if (!reverse)
	{
		volume >>= 5;
		volume = 154-volume;
	}
	else
	{
		volume >>= 6;
		volume -= 25;
	}

	if(volume > 0x80000000) volume = 0;	//if negative - make it 0
	if(volume > 100) volume = 100;

	temp = levels[SOUND_MIXER_VOLUME] & 0xFF;	//use only left channel
/*
	volume = (volume + old_slider_volume + temp)/3;//average the new volume

	if (volume>=24)
		volume += 3;	//fudge factor...

	old_slider_volume = volume;
*/


/*
if (hw_vol != old_slider_volume)
{
		temp=vncdebug;
		vncdebug=1;
		debprintf("Start_time: 0x%X, end: 0x%X, diff=%d.\n", start_time, hw_vol, start_time-hw_vol);
		vncdebug=temp;
old_slider_volume=hw_vol;
}
*/

	//slider quite often reads +-8, so debounce this random noise
	if ((volume != temp) && ((volume<temp-7) || (volume>temp+7)))
	{

//	extern void putstr(char *s);
//	char szTemp[48];
//		sprintf(szTemp,"Slider read: %d, volume: %d.\n", hw_vol, volume);
//		putstring(szTemp);

		DEB(printk("Slider read: %d, volume: %d.\n", hw_vol, volume));

//		dbprintf("Slider read: %d, volume: %d.\n", hw_vol, volume);

		if (devc->soft_volume_flag) // in software mode???
		{
		// if so - check if the current reading varies by more then 15
		// from the read at the moment we switch to soft_volume.
			if (devc->hw_volume > volume)
			{
				if (devc->hw_volume - volume > 20) //take over?
				{
//printk("waMixer: to hw vol, hw_vol = %d, cur vol = %d.\n",devc->hw_volume, volume);
					devc->soft_volume_flag = 0;
					wa_mixer_set(devc, SOUND_MIXER_VOLUME, (volume<<8)+volume);
				}
			}
			else
			{
				if (volume - devc->hw_volume > 20) //take over?
				{
//printk("waMixer: to hw vol, hw_vol = %d, cur vol = %d.\n",devc->hw_volume, volume);
					devc->soft_volume_flag = 0;
					wa_mixer_set(devc, SOUND_MIXER_VOLUME, (volume<<8)+volume);
				}
			}
		}
		else
		{
			wa_mixer_set(devc, SOUND_MIXER_VOLUME, (volume<<8)+volume);
		}
		return (1);
	}

	else
		return(0);	//no change

}

/*****/


static void
wavnc_mixer_reset (wavnc_info * devc)
{
//  char            name[32];
  int             foo;

  DDB (printk ("wa_mixer: wa_mixer_reset()\n"));

//  sprintf (name, "%s_%d", devc->chip_name, nr_wavnc_devs);

	wa_sendcmd(0x33);	//reset mixer cmd

// set input for ADC to come from a mux (left and right) == reg 9,initially none

	wa_sendcmd(0x32);
	wa_sendcmd(0x9800);
	wa_sendcmd(0xA836);

/*
//we do not need this anymore, because we hook the mics directly to ADC

// set input gain for mics: left (handset = 10dB, right (internal) = 20dB

	wa_sendcmd(0x32);
	wa_sendcmd(0x382C);
	wa_sendcmd(0x783C);
*/

// set mixer input select to none, RX filter gains 0 db

	wa_sendcmd(0x32);
	wa_sendcmd(0x4C00);
	wa_sendcmd(0x8C00);

// set bit 0 reg 2 to 1 - unmute MonoOut
	wa_sendcmd(0x32);	//set mixer pair
	wa_sendcmd(0x2801);	//reg 2	- unmute MonoOut
	wa_sendcmd(0x6800);	//reg 6

	for (foo = 0; foo < SOUND_MIXER_NRDEVICES; foo++)
		wa_mixer_set (devc, foo, levels[foo]);

	//set default input device = internal mic
	devc->recmask = 0;		//current recording device = none
	devc->handset_state = VNC_INTERNAL_MIC;	//assume no handset
//	wa_mixer_set(devc, SOUND_MIXER_RECSRC, SOUND_MASK_MIC);

	devc->soft_volume_flag = 0;	//start from enabling the hw setting
	vnc_slider(devc);		//adjust master volume as per slider

	devc->supported_devices = SUPPORTED_MIXER_DEVICES;
	devc->rec_devices = POSSIBLE_RECORDING_DEVICES;

}


//-----------------------------------------------------------------------
void
wa_mute(wavnc_info * devc, int mute)
{
}

//-----------------------------------------------------------------------

void
mute_mono(wavnc_info * devc, int mute)
{
	DDB(printk ("wa_vnc: mute=%d.\n",mute));

	if (mute)
	{
	    set_CPLD(0,2);	//reset bit
	}
	else
	{
	    set_CPLD(2,2);	//set bit
	}
	
	devc->mute_state = mute;	//remember the current setting...
}


//-----------------------------------------------------------------------

void
mute_lout(wavnc_info * devc, int mute)
{
  unsigned int	left1, right1;


	DDB(printk ("wa_vnc: mute_lout=%d.\n",mute));

	wa_sendcmd(0x0030);	//get current reg 1
	while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready...
	left1 = inw(CMDR);

	wa_sendcmd(0x0430);	// and reg 5
	while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready...
	right1 = inw(CMDR);


	if (mute)
	{
	    left1 &= ~0x01;	//mute line out
	    right1 &= ~0x01;
	}
	else
	{
	    left1 |= 0x01;	//unmute line out
	    right1 |= 0x01;
	}
	
//and finally - write the reg pair back....
	
	wa_sendcmd(0x32);
	wa_sendcmd(left1);
	wa_sendcmd(right1);

//	devc->mute_state = mute;	//remember the current setting...
}


static int
wavnc_mixer_ioctl (int dev, unsigned int cmd, caddr_t arg)
{
//  wavnc_info    *devc = mixer_devs[dev]->devc;
  wavnc_info    *devc = (wavnc_info *) audio_devs[dev]->devc;

  //use this call to override the automatic handset behaviour - ignore handset
  //bit 0x80 = total control over handset - do not react to plug/unplug
  //bit 0x40 = 1 == mute line-out output, otherwise unmute
  //bit 0x10 = 1 == internal mic, otherwise handset mic
  //bit 0x01 = 1 == mute internal speaker, otherwise unmute
  
    if (cmd == SOUND_MIXER_PRIVATE1)
    {
    int	val, temp;
	
	val = *(int *) arg;
	
	DDB(printk("MIXER_PRIVATE1: passed parameter = 0x%X.\n",val));
	
	if (val & ~(VNC_HANDSET_SW_ONLY|VNC_INTERNAL_SPKR|
		VNC_INTERNAL_MIC|VNC_LINE_OUT_MUTE))
	    return (-EINVAL);		//check if parameter is logical...

	devc->soft_mute_flag = val;
	      
	temp = val & VNC_INTERNAL_SPKR;
	if(temp != devc->mute_state)
	{
	    DDB(printk("MIXER_PRIVATE1: mute_mono(0x%X).\n",temp));
	    mute_mono(devc, temp);
	}

	temp = val & VNC_LINE_OUT_MUTE;
//for now we are not saving the lout_mute info...
//	if(temp != devc->mute_state)
	{
	    DDB(printk("MIXER_PRIVATE1: mute_line_out(0x%X).\n",temp));
	    mute_lout(devc, temp);
	}

//	temp = devc->handset_state;
	
//	do not check if it is not already in the right setting, since we
//	are laying about the current state...

//	if ((val & VNC_INTERNAL_MIC) != temp)
	{
	    devc->handset_state = val & VNC_INTERNAL_MIC;
	    if (devc->recmask == SOUND_MASK_MIC)
	    {
		DDB(printk("MIXER_PRIVATE1: mixer_set(0x%X).\n",devc->handset_state));
		wa_mixer_set(devc, SOUND_MIXER_RECSRC, SOUND_MASK_MIC);
	    }
//	    devc->handset_state = temp;
		
	}      
	return(0);

    }
    
    //get current PRIVATE setting...
    if (cmd == SOUND_MIXER_PRIVATE2)
    {
	*(int *) arg = devc->handset_state | devc->soft_mute_flag;
	DDB(printk("MIXER_PRIVATE2: returning 0x%X.\n", *(int*)arg));
	return(0);
    }

    
/*
  if (cmd == SOUND_MIXER_PRIVATE2)
    {
#define	VNC_SOUND_PAUSE		0x53	//to pause the DSP
#define	VNC_SOUND_RESUME	0x57	//to unpause the DSP

	int             val;

	val = *(int *) arg;
	
printk("MIXER_PRIVATE2: passed parameter = 0x%X.\n",val);


	if (val == VNC_SOUND_PAUSE)
	{
		wa_sendcmd(0x16);	//PAUSE the ADC
	
	}	
	else if (val == VNC_SOUND_RESUME)
	{
		wa_sendcmd(0x18);	//RESUME the ADC
	
	}	
	else
	{
	    return (-EINVAL);		//invalid parameters...
	}
	return(0);

    }
*/



  if (cmd == SOUND_MIXER_PRIVATE3)
    {
      long unsigned flags;
      int	mixer_reg[15];	//reg 14 is actually a command: read,write,reset
      int	val;
      int	i;

      val = *(int *) arg;

	if (verify_area(VERIFY_READ, (void*)val, sizeof(mixer_reg) == -EFAULT))
		return(-EFAULT);

	memcpy_fromfs(&mixer_reg, (void*)val, sizeof(mixer_reg));

	if (mixer_reg[0x0E] == MIXER_PRIVATE3_RESET)	//reset command??
	{
		wavnc_mixer_reset (devc);
		return(0);
	}

	else if (mixer_reg[0x0E] == MIXER_PRIVATE3_WRITE)//write command??
	{
//		printk("WaveArtist Mixer: Private write command.\n");

		wa_sendcmd(0x32);	//Pair1 - word 1 and 5
		wa_sendcmd(mixer_reg[0]);
		wa_sendcmd(mixer_reg[4]);
		
		wa_sendcmd(0x32);	//Pair2 - word 2 and 6
		wa_sendcmd(mixer_reg[1]);
		wa_sendcmd(mixer_reg[5]);

		wa_sendcmd(0x32);	//Pair3 - word 3 and 7
		wa_sendcmd(mixer_reg[2]);
		wa_sendcmd(mixer_reg[6]);

		wa_sendcmd(0x32);	//Pair4 - word 4 and 8
		wa_sendcmd(mixer_reg[3]);
		wa_sendcmd(mixer_reg[7]);

		wa_sendcmd(0x32);	//Pair5 - word 9 and 10
		wa_sendcmd(mixer_reg[8]);
		wa_sendcmd(mixer_reg[9]);

		wa_sendcmd(0x0031);	//set left and right PCM
		wa_sendcmd(mixer_reg[0x0A]);
		wa_sendcmd(mixer_reg[0x0B]);

		wa_sendcmd(0x0131);	//set left and right FM
		wa_sendcmd(mixer_reg[0x0C]);
		wa_sendcmd(mixer_reg[0x0D]);


		return(0);

	}
	else if (mixer_reg[0x0E] == MIXER_PRIVATE3_READ)//read command?
	{
//		printk("WaveArtist Mixer: Private read command.\n");

		//first read all current values...
		save_flags(flags);
		cli();

		for (i=0; i<14; i++)
		{
		wa_sendcmd((i<<8)+0x30);	// get ready for command nn30H
		while (! (inb(STATR) & CMD_RF)) {}; //wait for response ready...
		mixer_reg[i] = inw(CMDR);
		}
		restore_flags(flags);

		if (verify_area(VERIFY_WRITE, (void*)val, sizeof(mixer_reg) == -EFAULT))
			return(-EFAULT);

		memcpy_tofs((void*)val, &mixer_reg, sizeof(mixer_reg));
		return(0);
	}
	else
		return (-EINVAL);

    }

  if (((cmd >> 8) & 0xff) == 'M')
    {
      int             val;

      if (_SIOC_DIR (cmd) & _SIOC_WRITE)
	  {

	    val = *(int *) arg;

// special case for master volume: if we received this call - switch from
// hw volume control to a software volume control, till the hw volume
// is modified to signal that user wants to be back in hardware...

	if ((cmd &0xff) == SOUND_MIXER_VOLUME)	/* Master volume */
	{
		if (!devc->soft_volume_flag)	//the switching call?
		{
			devc->soft_volume_flag = 1;//mark going to soft volume

			// remember this hw setting to compare
			devc->hw_volume = levels[SOUND_MIXER_VOLUME]&0xFF;
//	printk("waMixer: soft vol, curr vol = %d.\n",devc->hw_volume);

		}

		// if already in soft volume, just set the volume
	}
	    return (*(int *) arg = wa_mixer_set (devc, cmd & 0xff, val));
	  }
      else
	switch (cmd & 0xff)	/*
				 * Return parameters
				 */
	  {

	  case SOUND_MIXER_RECSRC:
	    if (devc->handset_state & VNC_INTERNAL_MIC)	// handset not plugged in?
		    return (*(int *) arg = devc->recmask|SOUND_MASK_SPEAKER);
	    else	    
		    return (*(int *) arg = devc->recmask);
	    break;

	  case SOUND_MIXER_DEVMASK:
	    return (*(int *) arg = devc->supported_devices);
	    break;

	  case SOUND_MIXER_STEREODEVS:
	      return (*(int *) arg = devc->supported_devices & ~(SOUND_MASK_SPEAKER | SOUND_MASK_LINE1));
	    break;

	  case SOUND_MIXER_RECMASK:
	    return (*(int *) arg = devc->rec_devices);
	    break;

	  case SOUND_MIXER_CAPS:
	    return (*(int *) arg = SOUND_CAP_EXCL_INPUT);
	    break;

	  default:
	    return (*(int *) arg = levels[cmd & 0xff]);
	  }
    }
  else
  {
    DDB(printk("mixer: invalid IOCTL %X.\n",cmd));
    return -EINVAL;
  }
}



static int
wavnc_set_speed (int dev, int arg)
{
//  wavnc_info    *devc = (wavnc_info *) audio_devs[dev]->devc;
  wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;

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

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

      portc->speed = arg;
      return portc->speed;

}


static short
wavnc_set_channels (int dev, short arg)
{
  wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;

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

  portc->channels = arg;
  return arg;
}



static unsigned int
wavnc_set_bits (int dev, unsigned int arg)
{
//  wavnc_info    *devc = (wavnc_info *) audio_devs[dev]->devc;
  wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;

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

  if ((arg != AFMT_U8) && (arg != AFMT_S16_LE) && (arg != AFMT_S8))
    arg = AFMT_U8;

  portc->audio_format = arg;

  return arg;
}

static struct audio_driver wavnc_audio_driver =
{
  wavnc_open,
  wavnc_close,
  wavnc_output_block,
  wavnc_start_input,
  wavnc_ioctl,
  wavnc_prepare_for_input,
  wavnc_prepare_for_output,
  wavnc_halt,
  NULL,
  NULL,
  wavnc_halt_input,
  wavnc_halt_output,
  wavnc_trigger,
  wavnc_set_speed,
  wavnc_set_bits,
  wavnc_set_channels
};



static struct mixer_operations wavnc_mixer_operations =
{
  "WaveArtist",
  "WaveArtist NetWinder",
  wavnc_mixer_ioctl
};



static int
wavnc_open (int dev, int mode)
{
  wavnc_info    *devc = NULL;
  wavnc_port_info *portc;
  unsigned long   flags;

  if (dev < 0 || dev >= num_audiodevs)
    return -ENXIO;

  devc = (wavnc_info *) audio_devs[dev]->devc;
  portc = (wavnc_port_info *) audio_devs[dev]->portc;

  save_flags (flags);
  cli ();
  if (portc->open_mode || (devc->open_mode & mode))
    {
      restore_flags (flags);
      return -EBUSY;
    }

  devc->dual_dma = 0;

  if (audio_devs[dev]->flags & DMA_DUPLEX)
    {
      devc->dual_dma = 1;
    }

  devc->intr_active = 0;
  devc->audio_mode = 0;
  devc->triggerflag = 0;
  devc->open_mode |= mode;
  portc->open_mode = mode;
  wavnc_trigger (dev, 0);

  if (mode & OPEN_READ)
    devc->record_dev = dev;
  if (mode & OPEN_WRITE)
    devc->playback_dev = dev;
  restore_flags (flags);
/*
 * Mute output until the playback really starts. This decreases clicking (hope so).
 */
  wa_mute (devc,1);

  return 0;
}

static void
wavnc_close (int dev)
{
  unsigned long   flags;
  wavnc_info    *devc = (wavnc_info *) audio_devs[dev]->devc;
  wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;

//  DEB (printk ("wavnc_close(void)\n"));

  save_flags (flags);
  cli ();

  devc->intr_active = 0;
  wavnc_halt (dev);

  devc->audio_mode = 0;
  devc->open_mode &= ~portc->open_mode;
  portc->open_mode = 0;

  wa_mute (devc,1);
  restore_flags (flags);
}


static int
wavnc_ioctl (int dev, unsigned int cmd, caddr_t arg)
{
  return -EINVAL;
}




static void
wavnc_output_block (int dev, unsigned long buf, int count, int intrflag)
{
  unsigned long   flags, cnt;
  wavnc_info    *devc = (wavnc_info *) audio_devs[dev]->devc;
  wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;


  DEB1(printk("wavnc: output block, count = %X...\n",count));


  cnt = count;

  if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE))	/* 16 bit data */
	cnt >>= 1;

  if (portc->channels > 1)
    cnt >>= 1;

  cnt--;

  if (devc->audio_mode & PCM_ENABLE_OUTPUT && audio_devs[dev]->flags & DMA_AUTOMODE &&
      intrflag &&
      cnt == devc->xfer_count)
    {
      devc->audio_mode |= PCM_ENABLE_OUTPUT;
      devc->intr_active = 1;
      return;			/*
				 * Auto DMA mode on. No need to react
				 */
    }
  save_flags (flags);
  cli ();

  wa_writecmd(0x24,cnt);

  devc->xfer_count = cnt;
  devc->audio_mode |= PCM_ENABLE_OUTPUT;
  devc->intr_active = 1;
  restore_flags (flags);
}



static void
wavnc_start_input (int dev, unsigned long buf, int count, int intrflag)
{
  unsigned long   flags, cnt;
  wavnc_info    *devc = (wavnc_info *) audio_devs[dev]->devc;
  wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;

  DEB1(printk("wavnc: start input, buf=0x%X, count=0x%X...\n",buf,count));

  cnt = count;

  if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE))	/* 16 bit data */
	cnt >>= 1;

  if (portc->channels > 1)
    cnt >>= 1;

  cnt--;

  if (devc->audio_mode & PCM_ENABLE_INPUT && audio_devs[dev]->flags & DMA_AUTOMODE &&
      intrflag &&
      cnt == devc->xfer_count)
    {
      devc->audio_mode |= PCM_ENABLE_INPUT;
      devc->intr_active = 1;
      return;			/*
				 * Auto DMA mode on. No need to react
				 */
    }
  save_flags (flags);
  cli ();

  wa_writecmd(0x14,cnt);	//set sample count
  wa_mute (devc,0);

  devc->xfer_count = cnt;
  devc->audio_mode |= PCM_ENABLE_INPUT;
  devc->intr_active = 1;
  restore_flags (flags);
}

static int
wavnc_prepare_for_output (int dev, int bsize, int bcount)
{
  unsigned int	tmp, wa_speed, wa_bits;
  unsigned long   flags;
  wavnc_info    *devc = (wavnc_info *) audio_devs[dev]->devc;
  wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;

//DEB1(printk("wavnc: prepare for output...\n"));

//program the speed, channels, bits

	if (portc->speed == 8000)
		wa_speed = 0x2E71;
	else if (portc->speed == 11025)
		wa_speed = 0x4000;
	else if (portc->speed == 22050)
		wa_speed = 0x8000;
	else if (portc->speed == 44100)
		wa_speed = 0x0;
	else
	{
        	tmp = portc->speed * 0x10000;
	        wa_speed = tmp / 44100;
	        wa_speed &= 0xFFFF;
	}

	save_flags (flags);
	cli ();

        tmp = wa_writecmd(0x22, wa_speed);	//write cmd SetSampleSpeedTimeConstant
    
	if (tmp != 1)
	{
		//lets retry...
		tmp = wa_writecmd(0x22, wa_speed);
		if (tmp != 1)
		    printk(KERN_DEBUG "WaveArtist: error setting the playback speed to %dHz.\n",
			portc->speed);
	}
    
	tmp = wa_writecmd(0x21,portc->channels);
	if (tmp != 1)
		printk(KERN_DEBUG "WaveArtist: error setting the playback MONO/STEREO to 0x%X...\n",
		        portc->channels);

	if (portc->audio_format == AFMT_S16_LE)
		wa_bits = 1;
	else if (portc->audio_format == AFMT_S8)
		wa_bits = 0;
	else
		wa_bits = 2;		//default AFMT_U8

	tmp = wa_writecmd(0x20,wa_bits);
	if (tmp != 1)
		printk(KERN_DEBUG "WaveArtist: error setting the playback format to 0x%X...\n",
		        portc->audio_format);

	restore_flags (flags);
	devc->xfer_count = 0;
	wavnc_halt_output (dev);

	return 0;
}

static int
wavnc_prepare_for_input (int dev, int bsize, int bcount)
{
  unsigned int	tmp, wa_speed, wa_bits;
  unsigned long   flags;
  wavnc_info    *devc = (wavnc_info *) audio_devs[dev]->devc;
  wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;

	if (devc->audio_mode)
		return 0;

//program the speed, channels, bits

	if (portc->speed == 8000)
		wa_speed = 0x2E71;
	else if (portc->speed == 11025)
		wa_speed = 0x4000;
	else if (portc->speed == 22050)
		wa_speed = 0x8000;
	else if (portc->speed == 44100)
		wa_speed = 0x0;
	else
	{
	        tmp = portc->speed * 0x10000; //non-standard - calculate
	        wa_speed = tmp / 44100;
	        wa_speed &= 0xFFFF;
	}

	if (portc->audio_format == AFMT_S16_LE)
		wa_bits = 1;
	else if (portc->audio_format == AFMT_S8)
		wa_bits = 0;
	else
		wa_bits = 2;	//default AFMT_U8


	save_flags (flags);
	cli ();

	tmp = wa_writecmd(0x10,wa_bits);
	if (tmp != 1)
		printk(KERN_DEBUG "WaveArtist: error setting the record format to 0x%X...\n",
		        portc->audio_format);
    
	tmp = wa_writecmd(0x11,portc->channels);
	if (tmp != 1)
		printk(KERN_DEBUG "WaveArtist: error setting the record MONO/STEREO to 0x%X...\n",
		        portc->channels);


        tmp = wa_writecmd(0x12, wa_speed);	//write cmd SetSampleSpeedTimeConstant
	if (tmp != 1)
		printk(KERN_DEBUG "WaveArtist: error setting the record speed to %dHz.\n",
			portc->speed);

	tmp = wa_writecmd(0x13,1);
	if (tmp != 1)
		printk(KERN_DEBUG "WaveArtist: error setting the record data path to 0x%X...\n",
		        1);
    
	tmp = wa_writecmd(0x10,wa_bits);
	if (tmp != 1)
		printk(KERN_DEBUG "WaveArtist: error setting the record format to 0x%X...\n",
		        portc->audio_format);

	restore_flags (flags);
	devc->xfer_count = 0;
	wavnc_halt_input (dev);

//DEB(printk("WA CTLR reg: 0x%02X.\n",inb(CTLR)));
//DEB(printk("WA STAT reg: 0x%02X.\n",inb(STATR)));
//DEB(printk("WA IRQS reg: 0x%02X.\n",inb(IRQSTAT)));

	return 0;
}

static void
wavnc_halt (int dev)
{
  wavnc_info    *devc = (wavnc_info *) audio_devs[dev]->devc;
  wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;


  if (portc->open_mode & OPEN_WRITE)
    wavnc_halt_output (dev);

  if (portc->open_mode & OPEN_READ)
    wavnc_halt_input (dev);
  devc->audio_mode = 0;
  devc->triggerflag = 0;
}


static void
wavnc_halt_input (int dev)
{
  wavnc_info    *devc = (wavnc_info *) audio_devs[dev]->devc;
  unsigned long   flags;

    save_flags (flags);
    cli ();

    wa_mute (devc,1);
    disable_dma (audio_devs[dev]->dmap_in->dma);

    wa_sendcmd(0x17);	/* Stop capture */

    enable_dma (audio_devs[dev]->dmap_in->dma);
    devc->triggerflag &= ~0x02;
    devc->audio_mode &= ~PCM_ENABLE_INPUT;

    if (inb(STATR) & IRQ_REQ)	/* Clear interrupt */
    {
	int	temp;

        temp = inb(CTLR);	//by toggling the IRQ_ACK bit in CTRL
        outb(temp|IRQ_ACK,CTLR);
        outb(temp&(~IRQ_ACK),CTLR);
    }

//  devc->audio_mode &= ~PCM_ENABLE_INPUT;

  restore_flags (flags);
}

static void
wavnc_halt_output (int dev)
{
  wavnc_info    *devc = (wavnc_info *) audio_devs[dev]->devc;
  unsigned long   flags;

    save_flags (flags);
    cli ();

    wa_mute (devc,1);

    disable_dma (audio_devs[dev]->dmap_out->dma);
    wa_sendcmd(0x27);
    enable_dma (audio_devs[dev]->dmap_out->dma);
    devc->triggerflag &= ~0x01;
    devc->audio_mode &= ~PCM_ENABLE_OUTPUT;

    if (inb(STATR) & IRQ_REQ)	/* Clear interrupt */
    {
	int	temp;

	temp = inb(CTLR);	//by toggling the IRQ_ACK bit in CTRL
	outb(temp|IRQ_ACK,CTLR);
	outb(temp&(~IRQ_ACK),CTLR);
    }


//  devc->audio_mode &= ~PCM_ENABLE_OUTPUT;

    restore_flags (flags);
}


static void
wavnc_trigger (int dev, int state)
{

  wavnc_info    *devc = (wavnc_info *) audio_devs[dev]->devc;
  wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
  unsigned long   flags;

  DEB1(printk("wavnc: audio trigger, state=%X...\n",state));

  save_flags (flags);
  cli ();

  state &= devc->audio_mode;

  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
      {
	if(!(devc->triggerflag & 0x02))	//is input already triggered?
	{
	    devc->triggerflag |= 0x02;
	    wa_sendcmd(0x15);		//enable ADC Data Transfer to PC
	}
      }
    }

  if (portc->open_mode & OPEN_WRITE)
    {
      if (state & PCM_ENABLE_OUTPUT)
      {
	if(!(devc->triggerflag & 0x01))	//is output already triggered?
	{
	    devc->triggerflag |= 0x01;
	    wa_sendcmd(0x25);
	}
      }
    }

  wa_mute(devc,0);

  restore_flags (flags);
}


static void
wavnc_init_hw (wavnc_info * devc)
{

  /*
   * Initial values for the indirect registers of WaveARtist.
   */

//  wa_mute (devc,1);		/* Initialize some variables */
  wa_mute (devc,0);		/* Leave it unmuted now */


  devc->audio_flags |= DMA_DUPLEX;
  wavnc_mixer_reset (devc);


}

int
wavnc_detect (int io_base, int *ad_flags, int *osp)
{
  wavnc_info    *devc = &adev_info;

  if (nr_wavnc_devs >= MAX_AUDIO_DEV)
    {
      printk (KERN_DEBUG "WaveArtist: Too many audio devices\n");
      return 0;
    }

  devc->base = io_base;
  devc->irq_ok = 0;
  devc->irq = 0;
  devc->open_mode = 0;
  devc->chip_name = devc->name = "RWA010";
  devc->debug_flag = 0;
  devc->wa_osp = osp;

  return 1;
}

void
wavnc_init (char *name, int io_base, int irq, int dma_playback, int dma_capture, int share_dma, int *wa_osp)
{
  /*
     * NOTE! If irq < 0, there is another driver which has allocated the IRQ
     *   so that this driver doesn't need to allocate/deallocate it.
     *   The actually used IRQ is ABS(irq).
   */


  int             my_dev;
  char            dev_name[100];
  unsigned int	  temp,temp1,temp2;

  wavnc_info    *devc = &adev_info;
  wavnc_port_info *portc = NULL;

  devc->irq = (irq > 0) ? irq : 0;
  devc->open_mode = 0;
  devc->dma1 = dma_playback;
  devc->dma2 = dma_capture;
  devc->audio_flags = DMA_AUTOMODE;
  devc->playback_dev = devc->record_dev = 0;
  if (name != NULL)
    devc->name = name;


	//first reset the WA chip by toggling the RESET bit in CTRL
	temp2 = 0x100000;
        outb(RESET,CTLR);
	temp1 = jiffies + 2;		//for at least 10 ms

	while ((temp1 > jiffies) && temp2)
	{
		--temp2;
	}
        outb(0x00,CTLR);		//toggle it back

	temp2 = 0x00001000;
	temp1 = jiffies + 50;		//wait up-to .5 sec
	temp = 0;

	while ((temp1 > jiffies) && (--temp2) && (temp != 0x55AA))
	{
		temp = jiffies+2;		//wait couple miliseconds
		while (temp != jiffies) {};

		temp = 0;
		if(inb(STATR) & CMD_RF)
		{
			temp = inw(CMDR);
#if 0
			//as the result of reset - we want to see 55AA
			if (temp != 0x55AA)
				printk(KERN_DEBUG "WaveArtist reset error! (0x%04X).\n",temp);
#endif
		}
	}
//	if (temp != 0x55AA)
//		printk(KERN_DEBUG "WaveArtist error: timeout on reset! (0x%04X).\n",temp);

	wa_sendcmd(00);
	tenmicrosec(wa_osp);
//	while(!(inb(STATR) & CMD_RF)){};//wait for response
	temp = inw(CMDR);
	tenmicrosec(wa_osp);
	tenmicrosec(wa_osp);
//	while(!(inb(STATR) & CMD_RF)){};//wait for response
	inw(CMDR);		// discard second word == 0    
    
	printk("Sound V.%s: WaveArtist, rev.%c%c...\n",
		VERSION, temp>>8, temp&0xFF);


  if (name != NULL && name[0] != 0)
  {
    if (!temp)
      sprintf (dev_name,"%s (%s)", name, devc->chip_name);
    else
      sprintf (dev_name,"%s (%s rev.%c%c)", name, devc->chip_name,
		temp>>8, temp&0xFF);
  }
  else
    sprintf (dev_name,
	     "Generic audio codec (%s)", devc->chip_name);

  request_region (devc->base, 0x0F, devc->name);

  conf_printf2 (dev_name,
		devc->base, devc->irq, dma_playback, dma_capture);


  if (devc->dma1 == devc->dma2 || devc->dma2 == -1 || devc->dma1 == -1)
	devc->audio_flags &= ~DMA_DUPLEX;
  else
	devc->audio_flags |= DMA_DUPLEX;


  if ((my_dev = sound_install_audiodrv (AUDIO_DRIVER_VERSION,
					dev_name,
					&wavnc_audio_driver,
					sizeof (struct audio_driver),
					devc->audio_flags,
					AFMT_U8 | AFMT_S16_LE | AFMT_S8,
					devc,
					dma_playback,
					dma_capture)) < 0)
    {
      return;
    }


  portc = (wavnc_port_info *) (sound_mem_blocks[sound_nblocks] = vmalloc (sizeof (wavnc_port_info)));
  sound_mem_sizes[sound_nblocks] = sizeof (wavnc_port_info);
  if (sound_nblocks < 1024)
    sound_nblocks++;;
  audio_devs[my_dev]->portc = portc;
  memset ((char *) portc, 0, sizeof (*portc));

  nr_wavnc_devs++;

  wavnc_init_hw (devc);


	if (inb(STATR) & IRQ_REQ)	/* Clear interrupt */
	{
		int	temp;

	        temp = inb(CTLR);	//by toggling the IRQ_ACK bit in CTRL
	        outb(temp|IRQ_ACK,CTLR);
	        outb(temp&(~IRQ_ACK),CTLR);
	}

	if (snd_set_irq_handler (devc->irq, waintr,
			       devc->name,
			       NULL) < 0)
	{
		printk (KERN_DEBUG "WaveArtist: IRQ in use\n");
	}

	devc->irq_ok = 1;	/* Couldn't test. assume it's OK */

	//enable WaveArtist DMA interrupts..
	outb( (inb(CTLR)|(DMA1_IE | DMA0_IE)), CTLR);

	init_timer(&wa_timer);
	wa_timer.function = wa_slider;
	wa_timer.expires = jiffies + WA_TIMER_PERIOD;
	add_timer(&wa_timer);

	if (!share_dma)
	{
		if (sound_alloc_dma (dma_playback, devc->name))
			printk (KERN_DEBUG "WaveArtist: Can't allocate DMA%d\n", dma_playback);

		if (dma_capture != dma_playback)
		if (sound_alloc_dma (dma_capture, devc->name))
			printk (KERN_DEBUG "WaveArtist: Can't allocate DMA%d\n", dma_capture);
	}

	if (sound_install_mixer (MIXER_DRIVER_VERSION,
			   dev_name,
			   &wavnc_mixer_operations,
			   sizeof (struct mixer_operations),
			   devc) >= 0)
	{
		audio_devs[my_dev]->mixer_dev = num_mixers - 1;
	}

}

void
wavnc_unload (int io_base, int irq, int dma_playback, int dma_capture, int share_dma)
{
  int            dev = 0;
  wavnc_info    *devc = NULL;


  devc = &adev_info;


      release_region (devc->base, 0x0F);

      if (!share_dma)
	{

	//disable WaveArtist DMA interrupts..
	outb((inb(CTLR) & (~(DMA1_IE | DMA0_IE))),CTLR);


	  if (irq > 0)
	    snd_release_irq (devc->irq);

	  sound_free_dma (audio_devs[dev]->dmap_out->dma);

	  if (audio_devs[dev]->dmap_in->dma != audio_devs[dev]->dmap_out->dma)
	    sound_free_dma (audio_devs[dev]->dmap_in->dma);
	}

	del_timer(&wa_timer);
}

void
waintr (int irq, void *dev_id, struct pt_regs *dummy)
{

    int status;
    int	irqstatus;
    int	temp;
    wavnc_info *devc = &adev_info;


    irqstatus = inb(IRQSTAT);
    status = inb(STATR);

//DEB(printk("wavnc: interrupt: status=0x%02X, irqstatus=0x%02X...\n",status,irqstatus));

    if (status & IRQ_REQ)		/* Clear interrupt */
    {
        temp = inb(CTLR);		//by toggling the IRQ_ACK bit in CTRL
        outb(temp|IRQ_ACK,CTLR);
        outb(temp&(~IRQ_ACK),CTLR);
    }
    else
    {
	printk(KERN_DEBUG "WaveArtist: unexpected audio interrupt!\n");
    }


    if (irqstatus & 0x01)	//WA IRQ
    {
#ifdef CONFIG_AUDIO
  				/*
				 * PCM buffer done
				 */


#if 0
      /*
       * 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 (!(devc->flags & DMA_AUTOMODE))
	{
		wa_sendcmd(0x27);
	}
#endif

	temp = 1;

	if ((status & DMA0) && (devc->audio_mode & PCM_DAC))
	{
		DMAbuf_outputintr (devc->playback_dev, 1);
		temp = 0;
	}

	if ((status & DMA1) && (devc->audio_mode & PCM_ADC))
	{
		DMAbuf_inputintr (devc->record_dev);
		temp = 0;
	}

	if (temp)				//default:
	{
		printk (KERN_DEBUG "WaveArtist: Unknown interrupt\n");
	}

#endif
    }

    if (irqstatus & 0x2)	//SB IRQ
    {
	// We do not use SB mode natively...
	printk(KERN_DEBUG "WaveArtist: Unexpected SB interrupt...\n");
//#ifdef CONFIG_MIDI
//      wa_midi_interrupt ();
//#endif
    }
}


int
probe_wa_sound (struct address_info *hw_config)
{

  DDB (printk ("Entered probe_wa_sound(%x, %d)\n", hw_config->io_base, hw_config->card_subtype));

  if (check_region (hw_config->io_base, 0x0F))
    {
      printk (KERN_DEBUG "WaveArtist: I/O port conflict\n");
      return 0;
    }

//  if (hw_config->irq > 31 || hw_config->irq < 16 )
  if (hw_config->irq !=28 && hw_config->irq != 12 )
    {
      printk (KERN_DEBUG "WaveArtist: Bad IRQ %d\n", hw_config->irq);
      return 0;
    }

  if (hw_config->dma != 3)
    {
      printk (KERN_DEBUG "WaveArtist: Bad DMA %d\n", hw_config->dma);
      return 0;
    }

  return wavnc_detect (hw_config->io_base, NULL, hw_config->osp);
}

void
attach_wa_sound (struct address_info *hw_config)
{
  int             dma = hw_config->dma;
  int             dma2 = hw_config->dma2;

  wavnc_init ("WaveArtist NetWinder", hw_config->io_base,
	       hw_config->irq,
	       dma,
	       dma2, 0,
	       hw_config->osp);
}


void
unload_wa_sound (struct address_info *hw_config)
{
  wavnc_unload (hw_config->io_base,
		 hw_config->irq,
		 hw_config->dma,
		 hw_config->dma2, 0);
}
#endif

#if 0

//Source for a wamixer test program capable to change all mixer registers
// in real time...
//Woody, April 1998

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/errno.h>
#include <fcntl.h>
#include <linux/soundcard.h>

#define	MIXER_PRIVATE3_RESET	0x53570000
#define	MIXER_PRIVATE3_READ	0x53570001
#define	MIXER_PRIVATE3_WRITE	0x53570002

int mixer_fd=-1;
unsigned supported;
int source;

int	mixer_regs[15];


main(int argc, char *argv[])
{	unsigned int r, d;



    mixer_fd=open("/dev/mixer",O_RDWR);
    if(mixer_fd<0){
      perror("wamixer: Mixer not found!");
      exit(-1);
    }
    mixer_regs[0x0E] = MIXER_PRIVATE3_READ;
    source = &mixer_regs;

    source = ioctl(mixer_fd,SOUND_MIXER_PRIVATE3,&source);

    if (source < 0)
        printf("Mixer Private read returned error 0x%X!\n", source);   

    if (argc == 1)
    {
	printf("0001 0002 0003 0004 0005 0006 0007 0008 0009 0010 0011 0012 0013 0014\n");
	for (r=0; r<14; r++)
		printf("%04X ", mixer_regs[r]);
	printf("\n");
    }
	else if (argc%2 == 1)
	{
		while (argc > 1) {
			r = atoi(argv[1]);
			sscanf(argv[2], "%i", &d);
			printf("Register %d <- %04X\n", r, d);
			mixer_regs[r-1] = d;
			argc -= 2;
			argv += 2;
		}

	    mixer_regs[0x0E] = MIXER_PRIVATE3_WRITE;
	    source = &mixer_regs;

	    source = ioctl(mixer_fd,SOUND_MIXER_PRIVATE3,&source);

	    if (source < 0)
	        printf("Mixer Private write returned error 0x%X!\n", source);   

	}
	else {
		printf("usage: wamixer                       - dump registers\n");
		printf("       wamixer reg val [reg val ...] - program WA mixer register\n");
		printf("                  reg is decimal, value is hex (0x..) or decimal\n");
		printf("       For example:-\n");
		printf("       wamixer 9 0x982D  4 0x4C08  8 0x8C08\n");
		exit(1);
	}

	close(mixer_fd);

}

#endif
