/**********************************************************************
*
*  nvrec.c
*
*  Descripion  - Supporting the recording channel for nvaudio
*
*  Copyright (c) 2002-2003 NVIDIA Corporation
*
***********************************************************************
*/

#define __NO_VERSION__
#include <linux/module.h>

#include "nvhw.h"
#include "nvrec.h"

extern unsigned int clocking;

/**********************************************************************
* Function to get the current dma position 
**********************************************************************/
unsigned Nvaudio_get_recdma_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 + PI_PICB;
    
    do {
        civ    = inb(port+PI_CIV) & 31;
        offset = inw(port_picb);

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

    } while (civ != (inb(port+PI_CIV) & 31) || offset != inw(port_picb));
    
#ifdef NV_DEBUG_REC
    printk(" Rec DMA 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);
}

/**********************************************************************
* Function to set recording sample rate
**********************************************************************/
unsigned int Nvaudio_set_adc_rate(struct Nvaudio_state * state, unsigned int rate)
{
    u32 new_rate;
    struct ac97_codec *codec=state->card->ac97_codec[0];

#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_REC
    printk("Nvaudio_Adc_rate : asked for %d, got %d\n", rate, state->rate);
#endif

        return clocking;
    }*/

    if (rate > 48000)
        rate = 48000;
    if (rate < 8000)
        rate = 8000;
    state->rate = rate;
    
    rate = ( rate * clocking)/clocking;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,9)  
    new_rate = ac97_set_adc_rate(codec, rate);
#else
    /* Power down the ADC */
    dacp=Nvaudio_ac97_get(codec, AC97_POWER_CONTROL);
    Nvaudio_ac97_set(codec, AC97_POWER_CONTROL, dacp|0x0100);
    /* Load the rate and read the effective rate */
    Nvaudio_ac97_set(codec, AC97_PCM_LR_DAC_RATE, rate);
    new_rate=Nvaudio_ac97_get(codec, AC97_PCM_LR_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;
        rate = new_rate;
    }
#ifdef NV_DEBUG_REC
    printk("Nvaudio_set_adc_rate : rate support = %d rateasked = %d\n", state->rate, rate);
#endif
    return state->rate;
}

/**********************************************************************
* Function to Stop recording (lock held) 
**********************************************************************/
void __stop_adc(struct Nvaudio_state *state)
{
    struct Nvaudio_card *card = state->card;
    state->enable &= ~ADC_RUNNING;
    outb(0, card->iobase + PI_CR);
    // wait for the card to acknowledge shutdown
    while( inb(card->iobase + PI_CR) != 0 ) ;
    // now clear any latent interrupt bits (like the halt bit)
    outb( inb(card->iobase + PI_SR), card->iobase + PI_SR );

#ifdef NV_DEBUG_REC
        printk("Stopped ADC \n");
#endif
}

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

/**********************************************************************
* Function to start the recording
**********************************************************************/
void __start_adc(struct Nvaudio_state *state)
{
    struct dmabuf *dmabuf = &state->dmabuffer;
    if (dmabuf->count < dmabuf->dmasize && state->ready && !state->enable &&
        (state->trigger & PCM_ENABLE_INPUT)) {

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

#ifdef NV_DEBUG_REC
        printk("Starting ADC dmacount %x \n",dmabuf->count);
#endif

    }
}

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

/**********************************************************************
* Function to update the Lvi
**********************************************************************/
void __Nvaudio_update_reclvi(struct Nvaudio_state *state)
{
    int x = 0, port = 0;
    struct dmabuf *dmabuf = &state->dmabuffer;
    port = state->card->iobase;

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

        if(dmabuf->count < dmabuf->dmasize &&
           (state->trigger & PCM_ENABLE_INPUT)) {

            outb((inb(port+PI_CIV)+1)&31, port+PI_LVI);
            __start_adc(state);
            while( !(inb(port + PI_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+PI_LVI);

#ifdef NV_DEBUG_REC
    printk("__Nvaudio_update_reclvi %0x \n",x);
#endif
}

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

/**********************************************************************
* Function to update the current hwptr 
**********************************************************************/
void Nvaudio_update_recptr(struct Nvaudio_state *state)
{
    unsigned hwptr = 0;
    int diff       = 0;
    struct dmabuf *dmabuf = &state->dmabuffer;

    if (state->enable == ADC_RUNNING) {
        /* update hardware pointer */
        hwptr = Nvaudio_get_recdma_addr(state);
        diff = (dmabuf->dmasize + hwptr - dmabuf->hwptr) % dmabuf->dmasize;

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

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

        if (dmabuf->count > dmabuf->dmasize) {
            /* buffer underrun or buffer overrun */
            /* this is normal for the end of a read */
            /* only give an error if we went past the */
            /* last valid sg entry */
            if((inb(state->card->iobase + PI_CIV) & 31) !=
               (inb(state->card->iobase + PI_LVI) & 31)) {
                printk(KERN_WARNING "DMA overrun on read\n");
                dmabuf->error++;
            }
        }

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

}

/**********************************************************************
* Function to get count of data already recorded
**********************************************************************/
int Nvaudio_get_available_read_data(struct Nvaudio_state *state)
{
    int avail;
    struct dmabuf *dmabuf = &state->dmabuffer;

    Nvaudio_update_recptr(state);

    // catch overruns during record
    if (dmabuf->count > dmabuf->dmasize) {

#ifdef NV_DEBUG_REC
    printk("Record Over run %0x \n", dmabuf->count);
#endif
        dmabuf->count = dmabuf->dmasize;
        dmabuf->swptr = dmabuf->hwptr;
    }
    avail = dmabuf->count;
    avail -= (dmabuf->hwptr % dmabuf->fragsize);

#ifdef NV_DEBUG_REC
    printk(" Rec dmacount %x Avail %0x \n", dmabuf->count,avail);
#endif

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

/**********************************************************************
* Function to get Nvaudio_channel_recinterrupt 
**********************************************************************/
void Nvaudio_channel_recinterrupt(struct Nvaudio_card *card)
{
    int count = 0;
    struct dmabuf *dmabuf = NULL;
    unsigned long port = 0;
    u16 status = 0;

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

    struct Nvaudio_state *state = card->wavin;

    if(!state) return;
    dmabuf = &state->dmabuffer;
    if(!state->ready) return;
    if(!(state->enable & ADC_RUNNING)) {
            return;
    }
    port = card->iobase;
    spin_lock(&state->lock);
    status = inw(port + PI_SR);

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

    if(status & DMA_INT_COMPLETE)  {
        // only wake_up() waiters if this interrupt signals
        // us being beyond a userfragsize of data open or
        // available, and Nvaudio_recupdate_ptr() does that for
        // us

        Nvaudio_update_recptr(state);

#ifdef DEBUG_INTERRUPTS
    printk("COMP %x ", dmabuf->hwptr/dmabuf->fragsize);
#endif
    }
    if(status & (DMA_INT_LVI | DMA_INT_DCH))  {
        // wake_up() unconditionally on LVI and DCH
        Nvaudio_update_recptr(state);
        wake_up(&dmabuf->wait);

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

#ifdef DEBUG_INTERRUPTS
                printk(" CONTINUE ");
#endif
            } else {
                __stop_adc(state);
                state->enable = 0;

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

#ifdef DEBUG_INTERRUPTS
    printk(")\n");
#endif
}
