/* 	Copyright (c) 1993-1996 NeXT Software, Inc.  All rights reserved. 
 *
 */
#import "ProAudioSpectrum16.h"
#import "ProAudioSpectrum16Registers.h"
#import "ProAudioSpectrum16Inline.h"

#import <driverkit/generalFuncs.h>

static char pasDeviceName[] = "ProAudioSpectrum16";
static char pasDeviceKind[] = "Audio";

@implementation ProAudioSpectrum16

/*
 *  Probe and initialize new instance
 */
+ (BOOL) probe:description
{
    ProAudioSpectrum16	*dev;
    
    dev = [self alloc];
    if (dev == nil)
        return NO;
    
    /*
     * Sets the base IO address (needs to be performed before any other
     * hardware access)
     */
    setBaseAddress(DEFAULT_BASE_ADDRESS);

    return [dev initFromDeviceDescription:description] != nil;
}

- (BOOL) reset
{
    IOReturn	ioReturn;
    unsigned int dmaChannel	= [[self deviceDescription] channel];
    unsigned int interrupt	= [[self deviceDescription] interrupt];
    IOEISADMATransferWidth	transferWidth = IO_16BitByteCount;
    
    [self setName: pasDeviceName];
    [self setDeviceKind: pasDeviceKind];
    
    if (! (resetHardware())) {
        IOLog("%s: hardware failed verification\n", [self name]);
        return NO;
    }
    
    /*
     * The irq and dma channel are software selectable on the 
     * ProAudioSpectrum16.
     */
    if (! (setDMAChannel(dmaChannel))) {
        IOLog("%s: %d is an invalid dma setting\n",
	    [self name], dmaChannel);
	return NO;
    }

    if (! (setInterrupt(interrupt))) {
        IOLog("%s: %d is an invalid irq setting\n",
	    [self name], interrupt);
	return NO;
    }

    /*
     * The DMA controller is initialized.
     */
    [self disableChannel: 0];

    /*
     * On both ISA and EISA machines, the ProAudioSpectrum16 hardware
     * requires that DMA channels 0-4 are set to an 8-bit transfer width. 
     */
    if ([self isEISAPresent]) {
        if (dmaChannel < 5)
	    transferWidth = IO_8Bit;
	    
        ioReturn = [self setDMATransferWidth: transferWidth forChannel: 0];
	if (ioReturn != IO_R_SUCCESS) {
	    IOLog("%s: dma transfer width error %d\n", [self name], ioReturn);
	    return NO;
	}
    }

    ioReturn = [self setTransferMode: IO_Single forChannel: 0];

    if (ioReturn != IO_R_SUCCESS)  {
    	IOLog("%s: dma transfer mode error %d\n", [self name], ioReturn);
	return NO;
    }

    ioReturn = [self setAutoinitialize: YES forChannel: 0];
    if (ioReturn != IO_R_SUCCESS) {
    	IOLog("%s: dma auto initialize error %d", [self name], ioReturn);
	return NO;
    }

    return YES;
}

/*
 * converts gain (0 - 32768) into attenuation (0 - 31)
 */

- (void) updateInputGainLeft
{
    unsigned int gain = [self inputGainLeft] / 1057;
    
    setInputAttenuation(MICROPHONE, LEFT_CHANNEL, (unsigned char) gain);
    setInputAttenuation(EXTERNAL_LINE_IN, LEFT_CHANNEL, (unsigned char) gain);
}

- (void)updateInputGainRight
{
    unsigned int gain = [self inputGainRight] / 1057;
    
    setInputAttenuation(MICROPHONE, RIGHT_CHANNEL, (unsigned char) gain);
    setInputAttenuation(EXTERNAL_LINE_IN, RIGHT_CHANNEL, (unsigned char) gain);
}

- (void)updateOutputMute
{
    enableAudioOutput(! [self isOutputMuted]);
}

- (void)updateLoudnessEnhanced
{
    setLoudnessFilter([self isLoudnessEnhanced]);
}

/*
 * (0) - (-84) needs to be converted to (0) - (31)
 */
- (void) updateOutputAttenuationLeft
{
   unsigned int attenuation = [self outputAttenuationLeft] + 84;
   attenuation = ((attenuation * 10)/27);
    
    setOutputAttenuation(PCM, LEFT_CHANNEL, (unsigned char) attenuation);
}

/*
 * (0) - (-84) needs to be converted to (0) - (32)
 */
- (void) updateOutputAttenuationRight
{
   unsigned int attenuation = [self outputAttenuationRight] + 84;
   attenuation = ((attenuation * 10)/27);

    setOutputAttenuation(PCM, RIGHT_CHANNEL, (unsigned char) attenuation);
}

- (IOReturn) enableAllInterrupts
{
    interruptStatus_t	interruptStatus = {0};
    interruptControl_t	interruptControl = {0};
    
    /*
     * clear all interrupts
     */
    setInterruptStatus(interruptStatus);

    interruptControl.enableSampleBufferInterrupt = YES;
    setInterruptControl(interruptControl);
    
    return [super enableAllInterrupts];
}

- (void) disableAllInterrupts
{
    interruptControl_t	interruptControl = {0};

    setInterruptControl(interruptControl);
    
    [super disableAllInterrupts];
}


- (BOOL) startDMAForChannel: (unsigned int) localChannel
                       read: (BOOL) isRead
                     buffer: (IOEISADMABuffer) buffer
    bufferSizeForInterrupts: (unsigned int) bufferSize
{

    IOReturn ioReturn;
    unsigned int rate = [self sampleRate];
    
    IOEISADMATransferWidth	transferWidth;
    
    filterControl_t		filterControl		= {0};
    crossChannelControl_t	crossChannelControl	= {0};
    systemConfiguration2_t	systemConfiguration2	= {0};


    /*
     * Before enabling the DMA channel, "secure" the channel via the DRQ
     * bit in the Cross Channel register (0xf8a). This causes the
     * ProAudioSpectrum16 to drive the DMA channel from a floating state to
     * a known good state. If you enable the DMA channel with an unsecured
     * DRQ line, the DMA will perform a rapid-fire data tranfer due to the
     * floating DRQ line.
     */
    crossChannelControl = getCrossChannelControl();
    crossChannelControl.enableDMA = YES;
    setCrossChannelControl(crossChannelControl);
    
    ioReturn = [self startDMAForBuffer: buffer channel: localChannel];

    if (ioReturn != IO_R_SUCCESS) {
        IOLog("%s: could not start DMA channel error %d\n",
		[self name], ioReturn);
	return NO;
    }
        
    ioReturn = [self enableChannel: localChannel];
    
    if (ioReturn != IO_R_SUCCESS) {
        IOLog("%s: could not enable DMA channel error %d\n",
		[self name], ioReturn);
	return NO;
    }

    (void) [self enableAllInterrupts];

    setSampleRateTimer(rate);
   
    /*
     * Use the Sample Buffer Count register to set the number of bytes in the
     * DMA buffer division. This register holds a 16-bit value. When using a
     * 16-bit DMA channel, the Sample Buffer Count must be divided by two.
     */
    (void) [self getDMATransferWidth: &transferWidth forChannel: localChannel];
    if (transferWidth == IO_16BitByteCount ||
	transferWidth == IO_16BitWordCount)
	setSampleBufferCounter(bufferSize / 2);
    else
	setSampleBufferCounter(bufferSize);
	 

    if ([self dataEncoding] == NX_SoundStreamDataEncoding_Linear16)
	systemConfiguration2.dataEncoding = LINEAR_16;
    else
    	systemConfiguration2.dataEncoding = LINEAR_8;
	
    setSystemConfiguration2(systemConfiguration2);
    
    crossChannelControl.rightToRight = YES;
    crossChannelControl.leftToLeft = YES;

    crossChannelControl.direction = !isRead;
	
    /*
     * The mixer has feedback problems. MediaVision advised us to mute
     * the PCM output channel when recording.
     */
    if (isRead)
        setOutputAttenuation(PCM, BOTH_CHANNELS, 0);
	
    if ([self channelCount] == 1) 
        crossChannelControl.isMono = YES;
    else
        crossChannelControl.isMono = NO;
	
    setCrossChannelControl(crossChannelControl);
    crossChannelControl.enablePCM = YES;
    crossChannelControl.enableDMA = YES;
    setCrossChannelControl(crossChannelControl);
    
    filterControl = getFilterControl();
    filterControl.enableSampleRateTimer = YES;
    filterControl.enableSampleBufferCounter = YES;
    setFilterControl(filterControl);

    return YES;
}

- (void) stopDMAForChannel: (unsigned int) localChannel read: (BOOL) isRead
{
    filterControl_t		filterControl		= {0};
    crossChannelControl_t	crossChannelControl	= {0};

    [self disableChannel: localChannel];
    
    filterControl = getFilterControl();
    filterControl.enableSampleRateTimer = NO;
    filterControl.enableSampleBufferCounter = NO;
    setFilterControl(filterControl);
 
    (void)[self disableAllInterrupts];   
 
    crossChannelControl = getCrossChannelControl();
    crossChannelControl.enablePCM = NO;
    setCrossChannelControl(crossChannelControl);
    crossChannelControl.enableDMA = NO;
    setCrossChannelControl(crossChannelControl);
    
    /*
     * Due to mixer feedback problems, the PCM channels are muted during
     * recording. Their current values are restored here.
     */
    if (isRead) {
        [self updateOutputAttenuationLeft];
	[self updateOutputAttenuationRight];
    }
}

static void clearInterrupts(void)
{
    interruptStatus_t	interruptStatus = {0};

    /*
     * clear the hardware interrupt register
     */
    interruptStatus = getInterruptStatus();
    if (! interruptStatus.sampleBufferInterruptPending)
	return;
    
    /*
     * The chip specification states that a write to this register
     * will clear the clip status and sample interrupts.
     */
    interruptStatus.sampleBufferInterruptPending = NO;
    setInterruptStatus(interruptStatus);
}

- (IOAudioInterruptClearFunc) interruptClearFunc
{
    return clearInterrupts;
}

- (void) interruptOccurredForInput: (BOOL*)serviceInput
                         forOutput:(BOOL*)serviceOutput
{
    interruptStatus_t	interruptStatus = {0};

    /*
     * clear the hardware interrupt register
     */
    interruptStatus = getInterruptStatus();
    if (! interruptStatus.sampleBufferInterruptPending) {
	IOLog("ProAudioSpectrum16: spurious dma interrupt\n");
	return;
    }
    
    /*
     * The chip specification states that a write to this register
     * will clear the clip status and sample interrupts.
     */
    interruptStatus.sampleBufferInterruptPending = NO;
    setInterruptStatus(interruptStatus);

    if ([self isInputActive])
        *serviceInput = YES;
    else
        *serviceOutput = YES;
}

- (void) timeoutOccurred
{
    crossChannelControl_t	crossChannelControl	= {0};

    crossChannelControl = getCrossChannelControl();
    crossChannelControl.enablePCM = NO;
    setCrossChannelControl(crossChannelControl);
    crossChannelControl.enablePCM = YES;
    setCrossChannelControl(crossChannelControl);
}

/*
 * Parameter access.
 */

- (BOOL)acceptsContinuousSamplingRates
{
    return YES;
}

- (void)getSamplingRatesLow: (int *)lowRate
                       high: (int *)highRate
{
    *lowRate = 2000;
    *highRate = 44100;
}

- (void)getSamplingRates: (int *)rates
                   count: (unsigned int *)numRates
{
    /* Return a few common rates */
    rates[0] = 2000;
    rates[1] = 8000;
    rates[2] = 11025;
    rates[3] = 16000;
    rates[4] = 22050;
    rates[5] = 32000;
    rates[6] = 44100;
    *numRates = 7;
}

- (void)getDataEncodings: (NXSoundParameterTag *)encodings
                   count: (unsigned int *)numEncodings
{
    IOEISADMATransferWidth transferWidth;

    encodings[0] = NX_SoundStreamDataEncoding_Linear8;
    encodings[1] = NX_SoundStreamDataEncoding_Linear16;
    *numEncodings = 2;

    /*
     * For EISA machines, the driver is unable to handle 16-bit linear data
     * when the DMA transfer width is 8 bits.
     */
    if ([self isEISAPresent]) {
        (void)[self getDMATransferWidth: &transferWidth forChannel: 0];
	if (transferWidth == IO_8Bit) {
	    encodings[1] = 0;
	    *numEncodings = 1;
	}
    }
}

- (unsigned int) channelCountLimit
{
    return 2;	/* stereo and mono */
}

@end
