/**********************************************************************
*
*  nvwavout.c
*
*  Descripion  - Supporting the pcm waveout channel for nvcrush
*
*  Copyright (c) 2002-2003 NVIDIA Corporation
*
***********************************************************************
*/
#define __NO_VERSION__
#include <linux/module.h>

#include "nvhw.h"
#include "nvwavout.h"
#include "nvspdif.h"

extern unsigned int clocking;

static short AnalogLeftovers[6];
static u32   AnalogLeftoverCount = 0;

/**********************************************************************
* set playback sample rate
**********************************************************************/
unsigned int Nvaudio_set_dac_rate(struct Nvaudio_state *state, unsigned int rate)
{
    u32 new_rate;
    struct ac97_codec *codec=state->card->ac97_codec[0];
    struct Nvaudio_card *card = state->card;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,9)  
    u32 dacp = 0;
#endif

/*    if(!(state->card->ac97_features&0x0001)) {
        state->rate = clocking;

#ifdef NV_DEBUG_PLAY
        printk("Asked for %d Hz, but ac97_features says we only do %dHz.  Sorry!\n",
               rate,clocking);
#endif
        return clocking;
    } */

    if (rate > SAMPLERATE_48K)
        rate = SAMPLERATE_48K;
    if (rate < 8000)
        rate = 8000;
    state->rate = rate;

    rate = ( rate * clocking)/clocking;
    if(state->mapped) {

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,9)  
        new_rate = ac97_set_dac_rate(codec, rate);
#else
    /* Power down the DAC */
    dacp=Nvaudio_ac97_get(codec, AC97_POWER_CONTROL);
    Nvaudio_ac97_set(codec, AC97_POWER_CONTROL, dacp|0x0200);
    /* Load the rate and read the effective rate */
    Nvaudio_ac97_set(codec, AC97_PCM_FRONT_DAC_RATE, rate);
    new_rate=Nvaudio_ac97_get(codec, AC97_PCM_FRONT_DAC_RATE);
    /* Power it back up */
    Nvaudio_ac97_set(codec, AC97_POWER_CONTROL, dacp);
#endif
        if(new_rate != rate) {
            state->rate = (new_rate * clocking)/clocking;
        }
    }else {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,9)  
        /* set fixed sample rate now */
        ac97_set_dac_rate(codec, SAMPLERATE_48K);
#else
        /* Power down the DAC */
        dacp=Nvaudio_ac97_get(codec, AC97_POWER_CONTROL);
        Nvaudio_ac97_set(codec, AC97_POWER_CONTROL, dacp|0x0200);
        /* Load the rate and read the effective rate */
        Nvaudio_ac97_set(codec, AC97_PCM_FRONT_DAC_RATE, SAMPLERATE_48K);
        new_rate=Nvaudio_ac97_get(codec, AC97_PCM_FRONT_DAC_RATE);
        /* Power it back up */
        Nvaudio_ac97_set(codec, AC97_POWER_CONTROL, dacp);
#endif

    }
#ifdef NV_DEBUG_PLAY
    printk("Nvaudio_set_dac_rate : asked for %d, got %d\n", rate, state->rate);
#endif

    /* set the steps size Here */
    card->ncurstepsize  = 1.0f;
    card->ncurstepsize  = (float)rate / 48000.0f;
    card->nCurTimeFract = 0.0f;
    card->nCurTimeInt   = 0.0f;
    card->nCurbuffStep  = 48000.0f / (float)rate;

    return state->rate;
}

/**********************************************************************
* get current playbackdma buffer pointer (byte offset from LBA),
* called with spinlock held!
**********************************************************************/
unsigned Nvaudio_get_dma_addr(struct Nvaudio_state *state)
{
    unsigned int civ, offset, port, port_picb, bytes = 2;
    struct dmabuf *dmabuf = &state->dmabuffer;

    if (!state->enable)
        return 0;

    port  = state->card->iobase;
    port_picb = port + PO_PICB;

    do {
        civ    = inb(port+PO_CIV) & 31;
        offset = inw(port_picb);

        if(offset == 0)
            udelay(1);

    } while ( civ != (inb(port+PO_CIV) & 31) || offset != inw(port_picb));

#ifdef NV_DEBUG_PLAY
    printk(" DAC civ %0x  offset %0x dmaaddr %x \n" ,civ , offset,
        (((civ + 1) * dmabuf->fragsize - (2 * offset)) % dmabuf->dmasize));
#endif

    return (((civ + 1) * dmabuf->fragsize - (bytes * offset))
        % dmabuf->dmasize);
}

/**********************************************************************
* stop playback (lock held)
**********************************************************************/
void __stop_dac(struct Nvaudio_state *state)
{
    nodePtr curnode = NULL;
    int count = 0;
    struct Nvaudio_card *card = state->card;
    state->enable &= ~DAC_RUNNING;
    outb(0, card->iobase + PO_CR);

    if((card->outList) && (!state->mapped) && card->analogstate){
        if(card->outList->wavoutcount) {
            curnode = card->outList->wavoutnode;
        }else{
            if(card->outList->newnode) {
                curnode = card->outList->newnode;
            }
        }
        if(curnode) {
            count = (curnode->size / card->numchannels) * card->numchannels;
            count = curnode->size - count;
            count = card->numchannels - (count >> 1);
            count = (count << 1);
            curnode->analogposn = count;
            //printk("Setting analog position at stop %d \n",count);
      }
    }
    // wait for the card to acknowledge shutdown
    while( inb(card->iobase + PO_CR) != 0 ) ;

    // now clear any latent interrupt bits (like the halt bit)
#ifdef NV_DEBUG_PLAY
    printk("Stopping DAC \n");
#endif
    outb( inb(card->iobase + PO_SR), card->iobase + PO_SR );
}

void stop_dac(struct Nvaudio_state *state)
{
    unsigned long flags;
    spin_lock_irqsave(&state->lock, flags);
    __stop_dac(state);
    spin_unlock_irqrestore(&state->lock, flags);
}

/**********************************************************************
* start dac
**********************************************************************/
void __start_dac(struct Nvaudio_state *state)
{
    int i =0;
    unsigned int i_glob_cnt = 0;
    struct Nvaudio_card *card = state->card;
    struct dmabuf *dmabuf = &state->dmabuffer;

    if (card->analogstate && dmabuf->count > 0 && state->ready && !state->enable &&
        (state->trigger & PCM_ENABLE_OUTPUT)) {

        /* Reset the Global Cnt back to Stereo 16ch and set the format 
           This is needed to keep the 6 channel data in alignment ,
           to start from Front Left always */
        i_glob_cnt = inl(card->iobase + GLOB_CNT);
        outl((i_glob_cnt & 0xcfffff),card->iobase + GLOB_CNT);
        mdelay(50);
        outl(i_glob_cnt,card->iobase + GLOB_CNT);

        /* Reset the leftover datainfo*/
        for(i=0; i < 5 ; i++)
            AnalogLeftovers[i] = 0;
        AnalogLeftoverCount = 0;

        state->enable = DAC_RUNNING;

        /* Interrupt Enable, LVI Enable, DMA Enable*/
        outb((0x10|0x04|0x01), state->card->iobase + PO_CR);

#ifdef NV_DEBUG_PLAY
        printk("Starting DAC \n");
#endif
    }
}

void start_dac(struct Nvaudio_state *state)
{
    unsigned long flags;
    spin_lock_irqsave(&state->lock, flags);
    __start_dac(state);
    spin_unlock_irqrestore(&state->lock, flags);
}

/**********************************************************************
* update LVI
**********************************************************************/
void __Nvaudio_update_lvi(struct Nvaudio_state *state)
{
    int x, port = 0;
    struct dmabuf *dmabuf = &state->dmabuffer;

    port = state->card->iobase;

    if (!state->enable && state->ready) {

        if (dmabuf->count &&
           (state->trigger & PCM_ENABLE_OUTPUT)) {

            outb((inb(port+PO_CIV)+1)&31, port+PO_LVI);
            if(state->card->analogstate) {
                __start_dac(state);
                while( (!(inb(port + PO_CR) & (0x10|0x04)))) ;
            }
        }
    }

    /* swptr - 1 is the tail of our transfer */
    x = (dmabuf->dmasize + dmabuf->swptr - 1) % dmabuf->dmasize;
    x /= dmabuf->fragsize;
    outb(x, port+PO_LVI);

#ifdef NV_DEBUG_PLAY
    printk("__Nvaudio_update_daclvi %0x \n",x);
#endif
}

void Nvaudio_update_lvi(struct Nvaudio_state *state)
{
    unsigned long flags;
    if(!state->ready) return;
    spin_lock_irqsave(&state->lock, flags);
    __Nvaudio_update_lvi(state);
    spin_unlock_irqrestore(&state->lock, flags);
}

/**********************************************************************
* update buffer manangement pointers, 
* especially, dmabuf->count and dmabuf->hwptr
**********************************************************************/
void Nvaudio_update_ptr(struct Nvaudio_state *state)
{
    unsigned hwptr = 0;
    int diff       = 0;
    struct dmabuf *dmabuf = &state->dmabuffer;

    /* error handling and process wake up for DAC */
    if (state->enable == DAC_RUNNING) {

        /* update hardware pointer */
        hwptr = Nvaudio_get_dma_addr(state);
        diff  = (dmabuf->dmasize + hwptr - dmabuf->hwptr) % dmabuf->dmasize;

#if defined(DEBUG_INTERRUPTS) || defined(DEBUG_MMAP)
        printk("Update_ptr DAC HWP DIFF %d,%d,%d \n", hwptr, dmabuf->hwptr, diff);
#endif

        dmabuf->hwptr        = hwptr;
        dmabuf->total_bytes += diff;
        dmabuf->count       -= diff;

        if (dmabuf->count < 0) {
            /* buffer underrun or buffer overrun */
            /* this is normal for the end of a write */
            /* only give an error if we went past the */
            /* last valid sg entry */
            if((inb(state->card->iobase + PO_CIV) & 31) !=
               (inb(state->card->iobase + PO_LVI) & 31)) {
                printk(KERN_WARNING "Nvaudio: DMA overrun on write\n");
                printk("Nvaudio: CIV %d, LVI %d, hwptr %x, "
                    "count %d\n",
                    inb(state->card->iobase + PO_CIV) & 31,
                    inb(state->card->iobase + PO_LVI) & 31,
                    dmabuf->hwptr, dmabuf->count);
                    dmabuf->error++;
            }
        }

        if (dmabuf->count < (dmabuf->dmasize - dmabuf->userfragsize))
        {
            wake_up(&dmabuf->wait);
        }
    }

}

/**********************************************************************
* get the count of free space in the prds
**********************************************************************/
int Nvaudio_get_free_write_space(struct Nvaudio_state *state)
{
    int free;
    struct dmabuf *dmabuf = &state->dmabuffer;

    Nvaudio_update_ptr(state);
    // catch underruns during playback
    if (dmabuf->count < 0) {

#ifdef NV_DEBUG_PLAY
    printk("UNDERRUN in write space %d\n", dmabuf->count);
#endif
        dmabuf->count = 0;
        dmabuf->swptr = dmabuf->hwptr;
    }
    free = dmabuf->dmasize - dmabuf->count;
    free -= (dmabuf->hwptr % dmabuf->fragsize);

#ifdef NV_DEBUG_PLAY
    printk("Free write Space %0x \n", free);
#endif

    if(free < 0)
        return(0);
    return(free);
}

/**********************************************************************
* function to drain th existing data from dac
**********************************************************************/
int drain_dac(struct Nvaudio_state *state, int signals_allowed)
{
    unsigned long flags;
    unsigned long tmo;
    int count;
    struct dmabuf *dmabuf = &state->dmabuffer;

    DECLARE_WAITQUEUE(wait, current);
    if (!state->ready)
        return 0;
    if(state->mapped) {
        stop_dac(state);
        return 0;
    }
    add_wait_queue(&dmabuf->wait, &wait);
    for (;;) {

        spin_lock_irqsave(&state->lock, flags);
        Nvaudio_update_ptr(state);
        count = dmabuf->count;
        spin_unlock_irqrestore(&state->lock, flags);

        if (count <= 0)
            break;

        /*
         * This will make sure that our LVI is correct, that our
         * pointer is updated, and that the DAC is running.  We
         * have to force the setting of dmabuf->trigger to avoid
         * any possible deadlocks.
         */
        if(!state->enable) {
            state->trigger = PCM_ENABLE_OUTPUT;
            Nvaudio_update_lvi(state);
        }

        if (signal_pending(current) && signals_allowed) {
                break;
        }

        /* It seems that we have to set the current state to
         * TASK_INTERRUPTIBLE every time to make the process
         * really go to sleep.  This also has to be *after* the
         * update_ptr() call because update_ptr is likely to
         * do a wake_up() which will unset this before we ever
         * try to sleep, resuling in a tight loop in this code
         * instead of actually sleeping and waiting for an
         * interrupt to wake us up!
         */
         set_current_state(TASK_INTERRUPTIBLE);
        /*
         * set the timeout to significantly longer than it *should*
         * take for the DAC to drain the DMA buffer
         */
         tmo = (count * HZ) / (state->rate);
         //tmo = tmo ? tmo : 2;
        if (!schedule_timeout(tmo >= 2 ? tmo : 2)){
        //if(!interruptible_sleep_on_timeout(&dmabuf->wait,tmo)) {
            printk(KERN_ERR "Nvaudio: drain_dac, dma timeout?\n");
            count = 0;
            break;
        }
    }
    set_current_state(TASK_RUNNING);
    remove_wait_queue(&dmabuf->wait, &wait);

    if(count > 0 && signal_pending(current) && signals_allowed)
        return -ERESTARTSYS;

#ifdef NV_DEBUG_PLAY
    printk("Stop DAC Getting called from drain_dac \n");
#endif

    stop_dac(state);

#ifdef ENABLE_SPDIF
    if((state->spdif_enable) &&(state->card->spout))
        drain_spdif(state->card->spout,signals_allowed);
#endif

    return 0;
}

/**********************************************************************
* Nvaudio_channel_pcminterrupt
**********************************************************************/
void Nvaudio_channel_pcminterrupt(struct Nvaudio_card *card)
{
    unsigned long port = 0;
    int count  = 0;
    u16 status = 0;
    struct dmabuf *dmabuf = NULL;
    struct Nvaudio_state *state = card->wavout;
#ifdef DEBUG_INTERRUPTS
    printk("PCM CHANNEL ");
#endif
    if(!state) return;
    if(!state->ready) return;
    if(!(state->enable & DAC_RUNNING)) {
        return;
    }
    dmabuf = &state->dmabuffer;
    port   = card->iobase;
    spin_lock(&state->lock);
    status = inw(port + PO_SR);

#ifdef DEBUG_INTERRUPTS
     printk("IRQ ( ST%x ",status);
#endif

     if(status & DMA_INT_COMPLETE)  {
        Nvaudio_update_ptr(state);

#ifdef DEBUG_INTERRUPTS
       printk("COMP %x ", dmabuf->hwptr/dmabuf->fragsize);
#endif
     }

     if(status & (DMA_INT_LVI | DMA_INT_DCH))  {
        Nvaudio_update_ptr(state);
        wake_up(&dmabuf->wait);

#ifdef DEBUG_INTERRUPTS
        if((status & DMA_INT_LVI) || (status & DMA_INT_DCH))
           printk("LVI | DCH ");
#endif
        if(state->enable & DAC_RUNNING) {
            count = dmabuf->count;
            if(count > 0) {
               outb((inb(port+PO_CR) | 1), port+PO_CR);

#ifdef DEBUG_INTERRUPTS
            printk(" CONTINUE ");
#endif

            } else {

#ifdef DEBUG_INTERRUPTS
            printk("Stop DAC Getting called from interrupt with dmacount %d ",count);
#endif
             __stop_dac(state);
             state->enable = 0;
           }

         }

     }
     outw((status & DMA_INT_MASK), port + PO_SR);
     status = 0;
     spin_unlock(&state->lock);
#ifdef DEBUG_INTERRUPTS
    printk(")\n");
#endif
}

/**********************************************************************
* Function to check the speaker config and channel config and
*   determine to apply the downmix properly
**********************************************************************/
u32 Nvaudio_wavoutwrite(struct Nvaudio_card *card,const char* buffer,void * pDest, int cnt)
{
    // Check what is the channel format and 
    //   Speaker selection
    u32 SampleCnt = 0;
    int loopend   = cnt >> 1;
    struct Nvaudio_state *state = card->wavout;

    switch(card->numchannels) {
        case CHANNEL_51:    // 6 channel data check Spkr selection
            {
                switch(card->spkrselect) {

                    case NV_SPKR_STEREO:
                        // Mix everything to Stereo
                        SampleCnt = Nvaudio_MixDown5_1ToStereo(buffer,state,loopend,CHANNEL_51,FALSE);
                        break;
                    case NV_SPKR_QUAD:
                        SampleCnt = Nvaudio_MixDown5_1ToQuad(buffer,state,loopend,CHANNEL_51,FALSE);
                        break;
                    case NV_SPKR_51:
                        // Copy  over the data
                        SampleCnt = Nvaudio_MixNormal(buffer,state,loopend,CHANNEL_51,FALSE);
                        break;
                }
            break;
            }
        case CHANNEL_QUAD:
            {
                switch(card->spkrselect) {

                    case NV_SPKR_STEREO:
                        // Mix everything to Stereo 
                        SampleCnt = Nvaudio_MixDownQuadToStereo(buffer,state,loopend,CHANNEL_QUAD,FALSE);
                        break;
                    case NV_SPKR_QUAD:
                        // Copy over the data
                         SampleCnt = Nvaudio_MixNormal(buffer,state,loopend,CHANNEL_QUAD,FALSE);
                        break;
                    case NV_SPKR_51:
                        // Setting temporarily as QUAD now - Later we create new Center/Lfe channel
                        // Copy over the data
                        SampleCnt = Nvaudio_MixNormal(buffer,state,loopend,CHANNEL_QUAD,FALSE);
                        break;
                }
            break;
            }

        case CHANNEL_STEREO:
            {
                switch(card->spkrselect) {
                    case NV_SPKR_STEREO:
                        // Copy Over Data
                        SampleCnt = Nvaudio_MixNormal(buffer,state,loopend,CHANNEL_STEREO,FALSE);
                        break;
                    case NV_SPKR_QUAD:
                        // Replicate data on Surrounds
                        SampleCnt = Nvaudio_ReplicateStereotoRear(buffer,state,loopend,CHANNEL_STEREO);
                        break;
                    case NV_SPKR_51:
                        // Setting temporarily as Quad mode  now - with Replicate surrounds
                        //Later we create new Center/Lfe channel
                        SampleCnt = Nvaudio_ReplicateStereotoRear(buffer,state,loopend,CHANNEL_STEREO);
                        break;
                }

            }
        default:
            break;
    }
    
    SampleCnt = (SampleCnt << 2); /* 16bit stereo to get in Bytes*/
    return SampleCnt;
}
