/***************************************************************************
*	NAME:  UTIL16.C $Revision: 1.16 $
**	COPYRIGHT:
**	"Copyright (c) 1994,1995 by e-Tek Labs"
**
**       "This software is furnished under a license and may be used,
**       copied, or disclosed only in accordance with the terms of such
**       license and with the inclusion of the above copyright notice.
**       This software or any other copies thereof may not be provided or
**       otherwise made available to any other person. No title to and
**       ownership of the software is hereby transfered."
****************************************************************************
* $Log: util16.c $
* Revision 1.16  1995/12/07 14:09:18  sdsmith
* Added check to avoid infinite loop in iw_zero_codec
* Revision 1.15  1995/11/21 17:28:42  sdsmith
* Changes for new Windows init.
* Revision 1.14  1995/10/06 10:48:38  sdsmith
* Made a small change to iwl_zero_codec to account for previous format
* Revision 1.13  1995/09/29 15:58:34  sdsmith
* Made some fixes to zero_codec routine
* Revision 1.12  1995/09/22 08:29:20  sdsmith
* Added a routine to run 32 samples of silence through the CODEC when it
* is shutdown.
* Revision 1.11  1995/07/07 11:14:55  sdsmith
* Changed default mic setting
* Revision 1.10  1995/07/06 10:51:06  sdsmith
* Fixed defaults
* Revision 1.9  1995/07/06 10:15:07  sdsmith
* Changed Mono Out to mute by default
* Revision 1.8  1995/07/06 10:05:21  sdsmith
* Changed CODEC output to -6db by default
* Revision 1.7  1995/06/22 08:31:30  sdsmith
* Removed a warning message
* 
* Revision 1.6  1995/05/30 17:03:06  sdsmith
* Fixed setting data format
* Revision 1.5  1995/05/25 01:53:51  sdsmith
* Fixed recovery of old format value
* Revision 1.4  1995/04/26 16:22:13  sdsmith
* Fixed the public function commentary
* Revision 1.3  1995/03/27 07:56:20  sdsmith
* Added codec transparent function
* Revision 1.2  1995/02/24 14:03:44  mleibow
* fixed IDECI problem on cleanup
* Revision 1.1  1995/02/23 11:07:55  unknown
* Initial revision
***************************************************************************/
/***************************************************************************
OVERVIEW:
util16.c - CODEC utilities

This file contains the routines necessary to program codec functions
*/

#include <dos.h>

#include "iw.h"
#include "iwl.h"
#include "globals.h"
#include "digital.h"
#include "codec.h"

static void iwl_wait_mce_dfr(unsigned char, unsigned char);
extern int codec_loaded;
extern int iwl_process_codec_interrupt(void);
UCHAR iwl_codec_flushed = 0;

/***************************************************************************

FUNCTION DEFINITION:
iwl_codec_auto_calibrate - wait for the codec to finish auto-calibration

NOTE: We will try to do without this until we are sure that we need it.

RETURNS: void
*/
static void iwl_codec_auto_calibrate()
{
    OS_PUSH_DISABLE();
    /* Now, if in auto calibrate, wait for it to finish */
    if (iwl_cfig1i & CODEC_AUTOCALIB) {
	OS_OUTPORTB(iwl_codec_base,STATUS_2);
	while(OS_INPORTB(iwl_codec_base) != STATUS_2);
	while(OS_INPORTB(iwl_codec_data) & CODEC_CACT)
	    OS_OUTPORTB(iwl_codec_base,STATUS_2);
    }
    OS_POP_FLAGS();
}

/***************************************************************************

FUNCTION DEFINITION:
iwl_wait_mce_dfr - sets the playback or capture data format register

DESCRIPTION:
This routine sets the appropriate codec data format register to accept the
data format specified by the data parameter.  

BIT0-BIT3 Unused
BIT4: 1 = Stereo  0 = Mono
BIT7-BIT5
0 0 0  - Linear 8-bit unsigned
0 0 1  - uLaw, 8-bit companded
0 1 0  - Linear 16-bit 2's compliment Little Endian
0 1 1  - A-Law 8-bit companded
1 0 0  - RESERVED
1 0 1  - ADPCM, 4-bit, IMA compatible
1 1 0  - Linear 16-bit 2's compliment Big Endian
1 1 1  - RESERVED

RETURNS: void

SEE ALSO: Crystal CODEC 4231 Data sheet
*/
static void iwl_wait_mce_dfr(unsigned char data, unsigned char format_reg)
{
    if (format_reg == RECORD_FORMAT)
	iwl_crdfi = data;
    else
	iwl_cpdfi = data;

    OS_PUSH_DISABLE();

    /* Turn on the MCE bit to allow write to cdfr */
    OS_OUTPORTB(iwl_codec_base,CODEC_MCE|format_reg);
    if (format_reg == RECORD_FORMAT)
	OS_OUTPORTB(iwl_codec_data,iwl_crdfi);
    else
	OS_OUTPORTB(iwl_codec_data,iwl_cpdfi);

    /* turn off the MCE bit */
    OS_OUTPORTB(iwl_codec_base,format_reg);

    OS_POP_FLAGS();
}

/***************************************************************************

FUNCTION DEFINITION:
iwl_init_codec_voice - initialize the codec voice data structure

DESCRIPTION:
This routine initializes the fields of the digital voice data
structure used by the codec playback functions.

RETURNS: void

*/
void iwl_init_codec_voice(struct dig_voice_status RFAR *vs)
{
    int i;

    vs->pc_buffer = 0;
    vs->size = 0;
    vs->rate = 0;
    vs->type = 0;
    vs->pc_stbuff = 0;
    vs->callback = 0;
    vs->insert_buf = 0;
    vs->play_buf = 0;
    vs->position = 0L;
    vs->max = 0;
    vs->status = STAT_UNUSED;
    vs->b_size = 0;
    vs->addr_s = 0;
    vs->addr_e = 0;
    vs->insert_len = 0;
    for (i=0; i < MAX_BUFFS; i++) {
	vs->buffs[i].addr_s = 0;
	vs->buffs[i].play_size = 0;
	vs->buffs[i].status = 0;
    }
}


/***************************************************************************

FUNCTION DEFINITION:
iwl_codec_data_format - sets the data format

DESCRIPTION:
iwl_codec_data_format sets the format bit mask for the approprate data
format register based on the callers specification.

RETURNS: void
*/
void iwl_codec_data_format(
  struct dig_voice_status *vs,
  unsigned char format_reg)
{
    unsigned char format = 0;

    if (!(vs->type & IW_TYPE_8BIT)) {
	if (vs->type & IW_TYPE_ADPCM)
	    format |= CODEC_IMA_ADPCM;
	else
	    format |= CODEC_16BIT_SIGNED_LITTLE_END;
    }
    else {
	if (vs->type & IW_TYPE_ALAW)
	    format |= CODEC_8BIT_ALAW;		/* 8 bit, companded Alaw */
	else if (vs->type & IW_TYPE_ULAW)
	    format |= CODEC_8BIT_ULAW;		/* 8 bit, companded Ulaw */
    }

    if (vs->type & IW_TYPE_STEREO)
	format |= CODEC_STEREO;

    iwl_codec_set_freq(vs->rate, (UCHAR RFAR *)&format);

    if (format_reg == PLAYBACK_FORMAT) {
	iwl_wait_mce_dfr(format, PLAYBACK_FORMAT);
    }
    else {
	iwl_wait_mce_dfr(format, RECORD_FORMAT);
    }
}

/***************************************************************************

FUNCTION DEFINITION:
iwl_codec_set_freq - sets the playback frequency

DESCRIPTION:
iwl_codec_set_freq sets the frequency supported by the codec that is 
closest to the frequency requested by the caller.  The requested
frequency is divided by 1000 and compared to the frequencies in a 
table that maps kHz to bit masks.  The frequecy used is that closest
to the requested frequency but is not over.  The table truncs the 
frequencies listed below to the nearest 1000.

The frequency is set in the codec playback data format register based 
on the following table:

BIT3 - BIT0
0 0 0 0 - 8.0    kHz
0 0 0 1 - 5.51   kHz
0 0 1 0 - 16.0   kHz
0 0 1 1 - 11.025 kHz
0 1 0 0 - 27.42  kHz
0 1 0 1 - 18.9   kHz
0 1 1 0 - 32.0   kHz 
0 1 1 1 - 22.05  kHz
1 0 0 0 - N/A
1 0 0 1 - 37.8   kHz
1 0 1 0 - N/A
1 0 1 1 - 44.1   kHz
1 1 0 0 - 48.0   kHz
1 1 0 1 - 33.075 kHz
1 1 1 0 - 9.6    kHz
1 1 1 1 - 6.62   kHz

RETURNS: void
*/
void iwl_codec_set_freq(
  unsigned int hertz,
  unsigned char RFAR *format) /* caller's desired frequency */
{
    unsigned int thertz;
    int i;

    iwl_codec_playfreq = hertz;
    *format &= 0xF0;

    thertz = hertz / 1000;

    if (thertz > 48)
	thertz = 48;

    for (i=0;i<14;i++) {
	if (thertz <= codec_freq[i].hertz) {
	    *format |= codec_freq[i].bits;
	    break;
	}
    }
}

/***************************************************************************

FUNCTION DEFINITION:
iwl_codec_get_freq - query the current codec frequency setting

RETURNS: current codec frequency
*/
unsigned int iwl_codec_get_freq()
{
    return(iwl_codec_playfreq);
}


/***************************************************************************

FUNCTION DEFINITION:
iw_query_codec_freq - query the closest supported frequency

DESCRIPTION:
iw_query_codec_freq uses the table described above to find the frequency
the codec will be set to based upon the frequency requensted by the 
caller.  This routine does not actually set the codec frequency.

RETURNS: closest supported codec frequency

SEE ALSO:
iwl_codec_set_freq
*/
unsigned int iw_query_codec_freq(
  unsigned int hertz) /* caller's requested frequency */
{
    unsigned int thertz;
    int i;

    thertz = hertz / 1000;
    if (thertz > 48) thertz = 48;
    for (i=0;i<14 && thertz > codec_freq[i].hertz;i++);

    return(codec_freq[i].hertz * 1000);
}

void iwl_zero_codec(void)
{
    unsigned char format,status,silence;
    int i;

    if (!iwl_codec_flushed) {
	OS_PUSH_DISABLE();

	/* Set up playback format and count */
	IWL_CODEC_IN(PLAYBACK_FORMAT, format);
	if ((format & 0xE0) == CODEC_8BIT_UNSIGNED) silence = 0x80;
	else silence = 0x00;
	format = (format & 0xF0)|0x0B;
	iwl_wait_mce_dfr(format, PLAYBACK_FORMAT);
	iwl_codec_program_play_count(32);

	/* Switch CODEC into PIO mode */

	iwl_cfig1i |= CODEC_PLAYBACK_PIO;
	IWL_CODEC_OUT(CONFIG_1|CODEC_MCE, iwl_cfig1i);
	IWL_CODEC_OUT(CONFIG_1, iwl_cfig1i);

	/* Start the playback */
	iwl_cfig1i |= CODEC_PE;
	iwl_cfig2i |= CODEC_DAOF;
	IWL_CODEC_OUT(CONFIG_1, iwl_cfig1i);
	IWL_CODEC_OUT(CONFIG_2, iwl_cfig2i);

	/* Write out 32 zeros, careful of overrun */
	for (i=0; i<32;) {
	    OS_OUTPORTB(iwl_codec_pio, silence);
	    IWL_CODEC_IN(STATUS_3, status);
	    if (status == 0xFF) break;
	    if (!(status & CODEC_PFO)) {
		i++;
	    }
	    else {
		OS_INPORTB(iwl_codec_status);
	    }
	}

	/* Wait for FIFO to empty */
	do {
	    IWL_CODEC_IN(STATUS_3, status);
	    if (status == 0xFF) break;
	} while (!(status & CODEC_STATUS3_PFU));

	/* Shutdown playback */
	iwl_cfig1i &= ~CODEC_PE;
	iwl_cfig2i |= CODEC_DAOF;
	IWL_CODEC_OUT(CONFIG_1, iwl_cfig1i);
	IWL_CODEC_OUT(CONFIG_2, iwl_cfig2i);

	/* Secure from PIO mode */
	iwl_cfig1i &= ~CODEC_PLAYBACK_PIO;
	IWL_CODEC_OUT(CONFIG_1|CODEC_MCE, iwl_cfig1i);
	IWL_CODEC_OUT(CONFIG_1, iwl_cfig1i);

	/* Clear any status bits */
	OS_INPORTB(iwl_codec_status);

	OS_POP_FLAGS();
	iwl_codec_flushed = 1;
    }
}

/***************************************************************************

FUNCTION DEFINITION:
iwl_close_codec - shutdown the codec

DESCRIPTION:
The codec shutdown proceedure is as follows:

- stop playback and recording
- disable codec interrupts
- clear any pending interrupts
- reset the codec interrupt vector

RETURNS: void
*/
void iw_close_codec(void)
{
    OS_PUSH_DISABLE();

    if (--codec_loaded) {
	OS_POP_FLAGS();
	return;
    }

    /* Shutdown all CODEC DMA or I/O activity */
    iw_codec_stop_digital(IW_CODEC_PLAYBACK_VOICE);
    iw_codec_stop_record(IW_CODEC_RECORD_VOICE);

    /* clear any pending IRQs */
    iwl_codec_disable_irqs();
    iwl_zero_codec();
    OS_OUTPORTB(iwl_codec_status,0);
    iw_remove_handler(IW_IRQ_HANDLER_CODEC, (int (RFAR *)(void))iwl_process_codec_interrupt);

    /* Reset the CODEC registers to their default state */
    /* Loopback */
    IWL_CODEC_OUT(LOOPBACK, 0xFC);

    /* CFIG1I */
    OS_OUTPORTB(iwl_codec_base, CODEC_MCE|CONFIG_1);
    OS_OUTPORTB(iwl_codec_data, CODEC_CFIG1I_DEFAULT);
    /* CFIG2I */
    OS_OUTPORTB(iwl_codec_base, CODEC_MCE|CONFIG_2);
    OS_OUTPORTB(iwl_codec_data, CODEC_CFIG2I_DEFAULT);
    /* CFIG3I */
    OS_OUTPORTB(iwl_codec_base, CODEC_MCE|CONFIG_3);
    OS_OUTPORTB(iwl_codec_data, (CODEC_CFIG3I_DEFAULT & ~(CODEC_PPIE|CODEC_RPIE)));
    /* CMODEI */
    OS_OUTPORTB(iwl_codec_base, CODEC_MCE|MODE_SELECT_ID);
    OS_OUTPORTB(iwl_codec_data, CODEC_MODE_DEFAULT);

    OS_OUTPORTB(iwl_codec_base, 0);

#ifdef IWL_CODEC_IRQ2
    iwl_ideci &= ~IWL_IDECI_IAC22;
    IWL_OUT_B(IW_IDECI, iwl_ideci);
    iwl_reset_irq(iwl_irq2);
#else
    iwl_reset_irq(iwl_irq1);
#endif

    iwl_flags &= ~F_IW_CODEC_LOADED;

    OS_POP_FLAGS();
}

void iwl_codec_transparent(void)
{
    iwl_cmonoi = 0xC0;
    IWL_CODEC_OUT(MONO_IO_CTRL, iwl_cmonoi);
    iwl_clmici = 0x88;
    iwl_crmici = 0x88;
    IWL_CODEC_OUT(LEFT_MIC_INPUT, iwl_clmici);
    IWL_CODEC_OUT(RIGHT_MIC_INPUT, iwl_crmici);
    iwl_cllici = 0x08;
    iwl_crlici = 0x08;
    IWL_CODEC_OUT(LEFT_LINE_INPUT, iwl_cllici);
    IWL_CODEC_OUT(RIGHT_LINE_INPUT, iwl_crlici);
    iwl_clax1i = 0x08;
    iwl_crax1i = 0x08;
    IWL_CODEC_OUT(LEFT_AUX1_INPUT, iwl_clax1i);
    IWL_CODEC_OUT(RIGHT_AUX1_INPUT, iwl_crax1i);
    iwl_clax2i = 0x08;
    iwl_crax2i = 0x08;
    IWL_CODEC_OUT(LEFT_AUX2_INPUT, iwl_clax2i);
    IWL_CODEC_OUT(RIGHT_AUX2_INPUT, iwl_crax2i);
    iwl_cldaci = 0x04;
    iwl_crdaci = 0x04;
    IWL_CODEC_OUT(LEFT_DAC_OUTPUT, iwl_cldaci);
    IWL_CODEC_OUT(RIGHT_DAC_OUTPUT, iwl_crdaci);
    iwl_cloai = 0;
    iwl_croai = 0;
    IWL_CODEC_OUT(LEFT_MASTER_OUTPUT, iwl_cloai);
    IWL_CODEC_OUT(RIGHT_MASTER_OUTPUT, iwl_croai);
    iwl_clci = 0xFC;
    IWL_CODEC_OUT(LOOPBACK, iwl_clci);
    iwl_clici = 0xC0;
    iwl_crici = 0xC0;
    IWL_CODEC_OUT(LEFT_ADC_INPUT, iwl_clici);
    IWL_CODEC_OUT(RIGHT_ADC_INPUT, iwl_crici);
}
