/**********************************************************************
*
*  nvspdif.c
*
*  Descripion  - Supporting the spdifout channel for nvaudio
*
*  Copyright (c) 2002-2003 NVIDIA Corporation
*
***********************************************************************
*/
#include "nvhw.h"
#include "nvwavout.h"
#include "nvspdif.h"

extern unsigned int clocking;
extern int mastervolright;
extern int mastervolleft;
extern int pcmvolright;
extern int pcmvolleft;

static short SPDIFLeftovers[6];
static u32 SPDIFLeftoverCount = 0;

/**********************************************************************
* set spdifout sample rate 
**********************************************************************/
unsigned int Nvaudio_set_spdif_rate(struct Nvaudio_state *state, unsigned int rate)
{
    if (rate > 48000)
        rate = 48000;
    if (rate < 8000)
        rate = 8000;
    
    rate = ( rate * clocking)/clocking;
    state->rate = rate;
    return state->rate;
}

/**********************************************************************
* get current playbackdma buffer pointer (byte offset from LBA),
* called with spinlock held!
**********************************************************************/
unsigned Nvaudio_get_spdma_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 + SP_PICB;

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

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

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

#ifdef NV_DEBUG_SP
    printk("SP 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_spdif(struct Nvaudio_state *state)
{
    struct Nvaudio_card *card = state->card;
    state->enable &= ~SPDIF_RUNNING;
    outb(0, card->iobase + SP_CR);
    // wait for the card to acknowledge shutdown
    while( inb(card->iobase + SP_CR) != 0 ) ;
    // now clear any latent interrupt bits (like the halt bit)
    outb( inb(card->iobase + SP_SR), card->iobase + SP_SR );
#ifdef NV_DEBUG_SP
    printk("Stopping SP \n");
#endif
}

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

/**********************************************************************
* start spdif out
**********************************************************************/
void __start_spdif(struct Nvaudio_state *state)
{
    struct dmabuf *dmabuf = &state->dmabuffer;
    if (state->card->digitalstate && dmabuf->count > 0 && state->ready && !state->enable &&
        (state->trigger & PCM_ENABLE_OUTPUT)) {
         state->enable = SPDIF_RUNNING;

        /*Need to set the LVI od spdif same as pcm out*/
        outb(inb(state->card->iobase + PO_LVI),state->card->iobase + SP_LVI);
        outb((0x10|0x04|0x01), state->card->iobase + SP_CR);
#ifdef NV_DEBUG_SP
        printk("Starting SPDIF \n");
#endif
    }
}
void start_spdif(struct Nvaudio_state *state)
{
    unsigned long flags;
    spin_lock_irqsave(&state->lock, flags);
    start_spdif(state);
    spin_unlock_irqrestore(&state->lock, flags);
}

/**********************************************************************
* update lvi 
**********************************************************************/

void __Nvaudio_update_splvi(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+SP_CIV)+1)&31, port+SP_LVI);
            if(state->card->digitalstate)  {
                __start_spdif(state);
                while( (!(inb(port + SP_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+SP_LVI);
#ifdef NV_DEBUG_SP
    printk("__Nvaudio_update_splvi %0x \n",x);
#endif
}

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

/**********************************************************************
* update buffer manangement pointers, 
* especially, dmabuf->count and dmabuf->hwptr
**********************************************************************/
void Nvaudio_update_spptr(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 == SPDIF_RUNNING) {
        /* update hardware pointer */
        hwptr = Nvaudio_get_spdma_addr(state);
        diff  = (dmabuf->dmasize + hwptr - dmabuf->hwptr) % dmabuf->dmasize;

#if defined(DEBUG_INTERRUPTS) || defined(DEBUG_MMAP)
        printk("Update_ptr SP 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 + SP_CIV) & 31) !=
                   (inb(state->card->iobase + SP_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 + SP_CIV) & 31,
                    inb(state->card->iobase + SP_LVI) & 31,
                    dmabuf->hwptr, dmabuf->count);

           }
        }

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

}

/**********************************************************************
* get the count of free space in the prds
**********************************************************************/
int Nvaudio_get_free_spwrite_space(struct Nvaudio_state *state)
{
    int free;
    struct dmabuf *dmabuf = &state->dmabuffer;
    Nvaudio_update_spptr(state);
    // catch underruns during playback
    if (dmabuf->count < 0) {

#ifdef NV_DEBUG_SP
    printk("UNDERRUN in spwrite 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_SP
    printk("Free spwrite Space %0x \n", free);
#endif

    if(free < 0)
        return(0);
    return(free);
}
/**********************************************************************
* function to drain th existing data from spdif
**********************************************************************/
int drain_spdif(struct Nvaudio_state *state, int signals_allowed)
{
    unsigned long flags;
    unsigned long tmo;
    int count;
    DECLARE_WAITQUEUE(wait, current);
    struct dmabuf *dmabuf = &state->dmabuffer;

#ifdef NV_DEBUG_SP
    printk("Nvaudio: spdrain_dac \n");
#endif
    if (!state->ready)
        return 0;
    if(state->mapped) {
        stop_spdif(state);
        return 0;
    }
    add_wait_queue(&dmabuf->wait, &wait);
    for (;;) {

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

        if (count <= 0)
            break;

    if(!state->enable) {
            state->trigger = PCM_ENABLE_OUTPUT;
            Nvaudio_update_splvi(state);
        }

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

         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: spdrain_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_SP
    printk("Stop spdif Getting called from drain_spdif \n");
#endif
    stop_spdif(state);
    return 0;
}

/**********************************************************************
* spdif open
**********************************************************************/
int Nvaudio_spdifopen(struct Nvaudio_card  *card)
{
    int i =0;
    struct Nvaudio_state *state = NULL;
    struct dmabuf *dmabuf = NULL;
    if(card->spout) return -EBUSY;

    card->spout = (struct Nvaudio_state *)
                    kmalloc(sizeof(struct Nvaudio_state), GFP_KERNEL);
    if (card->spout == NULL)  return -ENOMEM;

    state  = card->spout;
    memset(state, 0, sizeof(struct Nvaudio_state));
    dmabuf = &state->dmabuffer;
    state->card  = card;
    spin_lock_init(&state->lock);
    init_waitqueue_head(&dmabuf->wait);

    state->trigger = 0;
    state->trigger = PCM_ENABLE_OUTPUT;
    Nvaudio_set_spdif_rate(state, 48000);
    state->fmt |= NVAUDIO_FMT_16BIT | NVAUDIO_FMT_STEREO;
    dmabuf->ossfragsize  = 0;
    dmabuf->ossmaxfrags  = 0;
    dmabuf->subdivision  = 0;

    state->mapped     = 0;
    state->open_mode |= FMODE_WRITE;
    spin_lock_init(&state->lock);

    /* Reset the leftover datainfo*/
    for(i=0; i < 5 ; i++)
        SPDIFLeftovers[i] = 0;
    SPDIFLeftoverCount = 0;
    return 0;
}

/**********************************************************************
* copy the data from the UserBuffer to Prd Dma 
**********************************************************************/
ssize_t Nvaudio_spdifwrite(struct Nvaudio_card *card, const char *buffer, size_t count)
{
    ssize_t ret;
    unsigned long flags;
    unsigned int swptr = 0;
    int cnt, x, loopend = 0, newcnt = 0;
    u32 SampleCount = 0;
    struct Nvaudio_state *state = card->spout;
    struct dmabuf *dmabuf = &state->dmabuffer;

    DECLARE_WAITQUEUE(waits, current);

    if(!card->digitalstate) {
        ret = count;
        return ret;
    }

#ifdef NV_DEBUG_SP
    printk("Nvaudio_spwrite called, count = %d\n", count);
#endif
    if(!state) return -EFAULT;

    if (state->mapped)
        return -ENXIO;

    if (!state->ready && (ret = prog_dmabuf(state, 2)))
        return ret;

    ret = 0;

    if((state->rate == SAMPLERATE_48K)|| (card->ac3passthrough))
        newcnt = count;
    else
        newcnt = 0;

    if((!card->analogstate) &&
        (count > 0) && (state->rate != 48000)) {
        if(NULL == Nvaudio_linearSRC(card,buffer,count)) {
            printk(KERN_ERR " More space for SRC \n");
            return -EFAULT;
        }
    
    }
    add_wait_queue(&dmabuf->wait, &waits);

    while( (newcnt > 0) || (card->outList->spoutcount)){
        set_current_state(TASK_INTERRUPTIBLE);
        
        if(!card->digitalstate) {
            if (!ret) ret = -EFAULT;
            goto writedone;
        }
        
        spin_lock_irqsave(&state->lock, flags);
        if (PM_SUSPENDED(card)) {
            spin_unlock_irqrestore(&state->lock, flags);
            schedule();
            if (signal_pending(current)) {
                if (!ret) ret = -EAGAIN;
                break;
            }
            continue;
        }

        swptr = dmabuf->swptr;
        cnt   = Nvaudio_get_free_spwrite_space(state);

        /* Bound the maximum size to how much we can copy to the
         * dma buffer before we hit the end.  If we have more to
         * copy then it will get done in a second pass of this
         * loop starting from the beginning of the buffer.
         */
        if(cnt > (dmabuf->dmasize - swptr))
            cnt = dmabuf->dmasize - swptr;
        spin_unlock_irqrestore(&state->lock, flags);

#ifdef NV_DEBUG_SP
        printk(KERN_INFO "Nvaudio_spwrite: %d bytes available space\n", cnt);
#endif
        if(state->rate == SAMPLERATE_48K) {
            /* recalculate the cnt */
            if(card->numchannels == CHANNEL_51) {
                cnt +=  (((cnt >> 1) / CHANNEL_51) * ( 4 * sizeof(short)));
            }else if(card->numchannels == CHANNEL_QUAD){
                cnt +=  (((cnt >> 1) / CHANNEL_QUAD) * ( 2 * sizeof(short)));
            }

            if (cnt > newcnt)
                cnt = newcnt;
        }
        /* Lop off the last two bits to force the code to always
         * write in full samples.  This keeps software that sets
         * O_NONBLOCK but doesn't check the return value of the
         * write call from getting things out of state where they
         * think a full 4 byte sample was written when really only
         * a portion was, resulting in odd sound and stereo
         * hysteresis.
         */
        cnt &= ~0x3;
        if (cnt <= 0) {
            unsigned long tmo;
            // There is data waiting to be played
            /*
             * Force the trigger setting since we would
             * deadlock with it set any other way
             */
            state->trigger = PCM_ENABLE_OUTPUT;
            Nvaudio_update_splvi(state);
            /* Not strictly correct but works */
            tmo = (dmabuf->dmasize * HZ * 2) / (state->rate * 4);
            /* There are two situations when sleep_on_timeout returns, one is when
               the interrupt is serviced correctly and the process is waked up by
               ISR ON TIME. Another is when timeout is expired, which means that
               either interrupt is NOT serviced correctly (pending interrupt) or it
               is TOO LATE for the process to be scheduled to run (scheduler latency)
               which results in a (potential) buffer underrun. And worse, there is
               NOTHING we can do to prevent it. */
            //tmo =   tmo ? tmo : 2;
            if (!schedule_timeout(tmo >= 2 ? tmo : 2)) {
            //if(interruptible_sleep_on_timeout(&dmabuf->wait,tmo)) {
#ifdef NV_DEBUG_SP
                printk(KERN_ERR "Nvaudio: sp playback schedule timeout, "
                       "dmasz %u fragsz %u count %i hwptr %u swptr %u\n",
                       dmabuf->dmasize, dmabuf->fragsize, dmabuf->count,
                       dmabuf->hwptr, dmabuf->swptr);
#endif
                /* a buffer underrun, we delay the recovery until next time the
                   while loop begin and we REALLY have data to play */
                //return ret;
            }
            if (signal_pending(current)) {
                if (!ret) ret = -ERESTARTSYS;
                goto writedone;
            }
            continue;
        }

#ifdef NV_DEBUG_SP
    printk("Nvaudio_spwrite copying from buffer %p count %x to location %p\n",
           buffer,cnt,dmabuf->rawbuf + swptr);
#endif

                  
       if(card->ac3passthrough) {
            if (copy_from_user(dmabuf->rawbuf+swptr,buffer,cnt)) {
                    if (!ret) ret = -EFAULT;
                    goto writedone;
                }
            SampleCount = cnt;
        }
        else {
            if(state->rate == SAMPLERATE_48K) {
                loopend = cnt  >> 1; /* now we supporting only 16bit Samples*/
                SampleCount = 0;
            
                switch(card->numchannels) {
                    case CHANNEL_STEREO:
                        /* taking 16bit data at a time, cnt in bytes */
                        SampleCount = Nvaudio_MixNormal(buffer,state,loopend, CHANNEL_STEREO, TRUE);
                        break;

                    case CHANNEL_QUAD:
                        SampleCount = Nvaudio_MixDownQuadToStereo(buffer,state,loopend,CHANNEL_QUAD,TRUE);
                        break;

                    case CHANNEL_51:
                        SampleCount = Nvaudio_MixDown5_1ToStereo(buffer,state,loopend,CHANNEL_51,TRUE);
                        break;
                } //switch for channel num
        
                SampleCount = (SampleCount << 2); /* 16bit stereo to get in Bytes*/

            }else {
                
                newcnt = count;
                if((newcnt > 0) && (cnt < newcnt)) {
                        newcnt = cnt;
                }
                newcnt = Nvaudio_copydatatospprd(card,newcnt);
                swptr  = dmabuf->swptr;
            }

        } //else

        if( (card->ac3passthrough)  || 
            (state->rate == 48000)) {
        
            swptr = (swptr + SampleCount) % dmabuf->dmasize;

            spin_lock_irqsave(&state->lock, flags);
            if (PM_SUSPENDED(card)) {
                    spin_unlock_irqrestore(&state->lock, flags);
                    continue;
            }

            dmabuf->swptr = swptr;
            dmabuf->count += SampleCount;
            spin_unlock_irqrestore(&state->lock, flags);

            newcnt -= cnt;
            buffer += cnt;
            ret    += cnt;
        }

#ifdef NV_DEBUG_SP
        printk("spwrite swptr %x dmacount %x \n", swptr,dmabuf->count);
#endif

    }
    if (swptr % dmabuf->fragsize) {
        x = dmabuf->fragsize - (swptr % dmabuf->fragsize);
        memset(dmabuf->rawbuf + swptr, '\0', x);
    }
writedone:
    Nvaudio_update_splvi(state);
    set_current_state(TASK_RUNNING);
    remove_wait_queue(&dmabuf->wait, &waits);
    if((state->rate != 48000) && (!card->ac3passthrough)){
        buffer += count;
        ret    += count;
    }
    return ret;
}

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

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

     if(status & DMA_INT_COMPLETE) {
        Nvaudio_update_spptr(state);
#ifdef DEBUG_INTERRUPTS
       printk("COMP %x ", dmabuf->hwptr/dmabuf->fragsize);
#endif
     }
     if(status & (DMA_INT_LVI | DMA_INT_DCH))  {
        Nvaudio_update_spptr(state);
        wake_up(&dmabuf->wait);
#ifdef DEBUG_INTERRUPTS
        if((status & DMA_INT_LVI) || (status & DMA_INT_DCH))
           printk("LVI | DCH ");
#endif
        if(state->enable & SPDIF_RUNNING) {
            count = dmabuf->count;
            if(count > 0) {
               outb((inb(port+SP_CR) | 1), port+SP_CR);

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

            } else {

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

         }

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