/**********************************************************************
*
*  nvcrush.c
*
*  Descripion  - Read/write and ioctls code are done in this file
*
*  Copyright (c) 2002-2003 NVIDIA Corporation
*
***********************************************************************
*/
#define __NO_VERSION__
#include <linux/module.h>

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

extern struct Nvaudio_card *devs;
extern unsigned int spdif_status;


/**********************************************************************
*  Funtion to verify for ac3 data.
*  This check the first few data for ac3 signature. 
***********************************************************************/
static int  Nvaudio_verifyac3(struct Nvaudio_card *card,const char *buffer, size_t count)
{
    unsigned short ac3data[0x40];
    int ac3size = 0, i = 0;
    ac3size = sizeof(short) * 0x40;
    memset(ac3data, 0, ac3size);
    
    if(count < ac3size) return 0;
    copy_from_user(ac3data,buffer,ac3size);

    for( i=0; i < 0x40; i++) {
        if(ac3data[i] == 0xF872) {
            i++;
            if(ac3data[i] == 0x4E1F) {
                card->waveformat     = AFMT_AC3;
                card->ac3passthrough = 1;
                return 1;
            }
        }
    }
    return 0;
}

/**********************************************************************
*   Nvaudio_read which helps to pass the recorded data from hw to user.
*   in this loop, dmabuf.count signifies the amount of data that is
*   waiting to be copied to the user's buffer.  It is filled by the dma
*   machine and drained by this loop. 
**********************************************************************/
static ssize_t Nvaudio_read(struct file *file, char *buffer, size_t count, loff_t *ppos)
{
    ssize_t ret;
    unsigned long flags;
    unsigned int swptr;
    int cnt;
    struct Nvaudio_card *card   = (struct Nvaudio_card *)file->private_data;
    struct Nvaudio_state *state = card ? card->wavin : 0;
    struct dmabuf *dmabuf       = &state->dmabuffer;

    DECLARE_WAITQUEUE(waitr, current);

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

    if (ppos != &file->f_pos)
        return -ESPIPE;
    if (state->mapped)
        return -ENXIO;

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

    if (!access_ok(VERIFY_WRITE, buffer, count))
        return -EFAULT;

    ret = 0;

    add_wait_queue(&dmabuf->wait, &waitr);
    while (count > 0) {
        set_current_state(TASK_INTERRUPTIBLE);
        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_available_read_data(state);
        // this is to make the copy_to_user simpler below
        if(cnt > (dmabuf->dmasize - swptr))
            cnt = dmabuf->dmasize - swptr;
        spin_unlock_irqrestore(&state->lock, flags);

        if (cnt > count)
            cnt = count;
        /* 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;

#ifdef NV_DEBUG_REC
        printk("Nvaudio_read recorded = %d\n", cnt);
#endif
        if (cnt <= 0) {
            unsigned long tmo;
            /*
             * Don't let us deadlock.  The ADC won't start if
             * dmabuf->trigger isn't set.  A call to SETTRIGGER
             * could have turned it off after we set it to on
             * previously.
             */
            state->trigger = PCM_ENABLE_INPUT;
            /*
             * This does three things.  Updates LVI to be correct,
             * makes sure the ADC is running, and updates the
             * hwptr.
             */
            Nvaudio_update_reclvi(state);
            if (file->f_flags & O_NONBLOCK) {
                if (!ret) ret = -EAGAIN;
                goto done;
            }
            /* Set the timeout to how long it would take to fill
             * two of our buffers.  If we haven't been woke up
             * by then, then we know something is wrong.
             */
            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 overrun. 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_REC
                printk(KERN_ERR "Nvaudio: recording 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 overrun, we delay the recovery until next time the
                   while loop begin and we REALLY have space to record */
            }
            if (signal_pending(current)) {
                ret = ret ? ret : -ERESTARTSYS;
                goto done;
            }
            continue;
        }

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

        if (copy_to_user(buffer, dmabuf->rawbuf + swptr, cnt)) {
            if (!ret) ret = -EFAULT;
            goto done;
        }

        swptr = (swptr + cnt) % dmabuf->dmasize;

        spin_lock_irqsave(&state->lock, flags);

        if (PM_SUSPENDED(card)) {
            spin_unlock_irqrestore(&state->lock, flags);
            continue;
        }
        dmabuf->swptr = swptr;
        dmabuf->count -= cnt;
        spin_unlock_irqrestore(&state->lock, flags);

#ifdef NV_DEBUG_REC
    printk("Nvaudio_read Swptr %x count %x \n",dmabuf->swptr,dmabuf->count);
#endif

        count  -= cnt;
        buffer += cnt;
        ret    += cnt;
    }
 done:
    Nvaudio_update_reclvi(state);
    set_current_state(TASK_RUNNING);
    remove_wait_queue(&dmabuf->wait, &waitr);

    return ret;
}

/*******************************************************************************
*  Function that passed the data from user to hw for playback.   
*  in this loop, dmabuf.count signifies the amount of data that is waiting
*  to be dma to the soundcard.  
*  it is drained by the dma machine and filled by this loop. 
******************************************************************************/
static ssize_t Nvaudio_write(struct file *file, const char *buffer, size_t count, loff_t *ppos)
{
    ssize_t ret = 0;
#ifdef ENABLE_SPDIF
    const char *spbuffer = buffer;
    size_t spcount       = count;
    ssize_t spret        = 0;
#endif
    unsigned long flags;
    unsigned int swptr = 0;
    int cnt = 0, x = 0, newcnt = 0, failcount = 0;
    u32 samplecount    = 0;
    struct Nvaudio_card *card   = (struct Nvaudio_card *)file->private_data;
    struct Nvaudio_state *state = card ? card->wavout: 0;
    struct dmabuf *dmabuf = &state->dmabuffer;

    DECLARE_WAITQUEUE(waita, current);
    /*check whether it is digital only - no analog play */
    if((card->waveformat == AFMT_AC3) || (!card->analogstate)) {
        goto ac3only;
    }

    /* ac3 data can send with  16 bit format*/
    if(card->verifyac3 == 0) {
        card->verifyac3 =1;
        if(Nvaudio_verifyac3(card,buffer,count)){
            goto ac3only;
        }
    }

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

    if (ppos != &file->f_pos)
        return -ESPIPE;
    if (state->mapped)
        return -ENXIO;

    if (!state->ready && (ret = prog_dmabuf(state, 0)))
        return ret;
    if (!access_ok(VERIFY_READ, buffer, count))
        return -EFAULT;

    ret = 0;

    if(count == 0) return ret;
    count &= ~0x3;
    newcnt = count;

    /* Do src and copy the data to linked list */
    if((count > 0) && (state->rate != SAMPLERATE_48K)) {
        if(NULL == Nvaudio_linearSRC(card,buffer,count)) {
            return ret;
        }
    }

    add_wait_queue(&dmabuf->wait, &waita);

    while( (newcnt > 0) || (card->outList->wavoutcount)) {

        set_current_state(TASK_INTERRUPTIBLE);
        
        if(!card->analogstate)  {
           if (!ret) ret = -EAGAIN;
           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_write_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_PLAY
        printk(KERN_INFO " Nvaudio_write: %d bytes available space\n", cnt);
#endif
        if(state->rate == SAMPLERATE_48K) {
            /*check whether the calc is right or not */
            cnt = Nvaudio_Recalfreespace(card,cnt);
            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_lvi(state);

            if ((file->f_flags & O_NONBLOCK) && (state->rate == SAMPLERATE_48K)) {
                if (!ret) ret = -EAGAIN;
                failcount = 1;
                goto writedone;
            }
            /* 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_PLAY
                printk(KERN_ERR "Nvaudio: 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 */
            }
            if (signal_pending(current)) {
                if (!ret) ret = -ERESTARTSYS;
                failcount = 1;
                goto writedone;
            }
            continue;
        }

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

        if(state->rate == SAMPLERATE_48K) {
            samplecount = Nvaudio_wavoutwrite(card, buffer, (void *)(dmabuf->rawbuf+swptr), cnt);

            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;
        }else {
            newcnt = count;
            if(cnt < newcnt){
                newcnt = cnt;
            }
            newcnt = Nvaudio_copydatatowavprd(card,newcnt);
            swptr = dmabuf->swptr;
        }

#ifdef NV_DEBUG_PLAY
        printk("Play 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_lvi(state);
    set_current_state(TASK_RUNNING);
    remove_wait_queue(&dmabuf->wait, &waita);

    if(!failcount) {
ac3only:
#ifdef ENABLE_SPDIF
        if((state->spdif_enable)&&(card->spout)){
             spret = Nvaudio_spdifwrite(card,spbuffer,spcount);
             if(card->waveformat == AFMT_AC3) ret = spret;
             else if((card->digitalstate) && !(card->analogstate)) ret = count;
        }else {
            if(card->waveformat == AFMT_AC3) ret = count;
        }
#endif

        if(state->rate != 48000){
            buffer += count;
            ret    += count;
        }
    }
    return ret;
}

/**********************************************************************
* Function to poll the hw status
* No kernel lock - we have our own spinlock 
**********************************************************************/
static unsigned int Nvaudio_poll(struct file *file, struct poll_table_struct *wait)
{
    struct dmabuf *dmabuf       = NULL;
    struct Nvaudio_state *state = NULL;
    unsigned long flags;
    unsigned int mask = 0;
    struct Nvaudio_card *card   = (struct Nvaudio_card *)file->private_data;

#ifdef NV_DEBUG_NORMAL
    printk("Nvaudio: Nvaudio_poll ");
#endif
    if(file->f_mode & FMODE_WRITE)  {
        if(card->spout && (!card->analogstate && card->digitalstate)) {
            poll_wait(file, &card->spout->dmabuffer.wait, wait);  
        } else {
            poll_wait(file, &card->wavout->dmabuffer.wait, wait);   
        }
    }

    if(file->f_mode & FMODE_READ)
        poll_wait(file, &card->wavin->dmabuffer.wait, wait);

    if(file->f_mode & FMODE_READ){
       state  = card->wavin;
       dmabuf = &state->dmabuffer;
       if(!state->ready)     return 0;
       spin_lock_irqsave(&state->lock, flags);
       if (state->enable & ADC_RUNNING ||
           state->trigger & PCM_ENABLE_INPUT) {
            if (Nvaudio_get_available_read_data(state) >=
            (signed)dmabuf->userfragsize)
                 mask |= POLLIN | POLLRDNORM;
       }
       spin_unlock_irqrestore(&state->lock, flags);
    }

    if(file->f_mode & FMODE_WRITE){
        if(card->spout && (!card->analogstate && card->digitalstate)) {
            state  = card->spout;
            dmabuf = &state->dmabuffer;
        } else  {
            state  = card->wavout;
            dmabuf = &state->dmabuffer;
        }
       if(!state->ready)     return 0;
       spin_lock_irqsave(&state->lock, flags);
       if (state->enable & DAC_RUNNING ||
           state->trigger & PCM_ENABLE_OUTPUT) {
            if (Nvaudio_get_free_write_space(state) >=
            (signed)dmabuf->userfragsize)
                mask |= POLLOUT | POLLWRNORM;
       }
       spin_unlock_irqrestore(&state->lock, flags);
    }
#ifdef NV_DEBUG_NORMAL
    printk("with mask %d \n ",mask);
#endif
    return mask;
}

/**********************************************************************
* Function to enable the mmap settings
**********************************************************************/
static int Nvaudio_mmapwrite(struct vm_area_struct *vma,struct Nvaudio_state *state,unsigned rec)
{
    int ret = -EINVAL;
    unsigned long size = 0;


    struct dmabuf *dmabuf  = &state->dmabuffer;

    if ((ret = prog_dmabuf(state, rec)) != 0)
       return ret;
    /* mapping is added only for dac support - 
       need to figure out how to route to spdif
    */
    if(rec == 0 ) {
        ret = -EINVAL;
        if (vma->vm_pgoff != 0)
           return ret;

        size = vma->vm_end - vma->vm_start;
        if (size > (PAGE_SIZE << dmabuf->buforder))
           return ret;
        ret = -EAGAIN;

#if defined(REMAP_NEW)
        if (remap_page_range(vma, vma->vm_start, virt_to_phys(dmabuf->rawbuf),
                 size, vma->vm_page_prot))
#else
        if (remap_page_range(vma->vm_start, virt_to_phys(dmabuf->rawbuf),
                 size, vma->vm_page_prot))
#endif
           return ret;
    }
    state->mapped  = 1;
    state->trigger = 0;
    ret = 0;
    return ret;

}

/**********************************************************************
* Function to do mmap ioctls
**********************************************************************/
static int Nvaudio_mmap(struct file *file, struct vm_area_struct *vma)
{
    struct Nvaudio_state *dacstate = NULL;
    struct Nvaudio_state *recstate = NULL;
    struct dmabuf *recdmabuf       = NULL;
    int ret = -EINVAL;
    unsigned long size = 0;

    struct Nvaudio_card *card      = (struct Nvaudio_card *)file->private_data;

    lock_kernel();
    if(vma->vm_flags & VM_READ){

        recstate  = card->wavin;
        recdmabuf = &recstate->dmabuffer;

        if ((ret = prog_dmabuf(recstate,1)) != 0)
           goto out;

        ret = -EINVAL;

        if(!(vma->vm_flags & VM_WRITE)) {
            if (vma->vm_pgoff != 0)
              goto out;

            size = vma->vm_end - vma->vm_start;
            if (size > (PAGE_SIZE << recdmabuf->buforder))
              goto out;
            ret = -EAGAIN;

#if defined(REMAP_NEW)
            if (remap_page_range(vma, vma->vm_start, virt_to_phys(recdmabuf->rawbuf),
                 size, vma->vm_page_prot))
#else
            if (remap_page_range(vma->vm_start, virt_to_phys(recdmabuf->rawbuf),
                 size, vma->vm_page_prot))
#endif
            goto out;
        }
        recstate->mapped = 1;
        recstate->trigger = 0;
        ret = 0;
    }
    if(vma->vm_flags & VM_WRITE){
       dacstate = card->wavout;
#ifdef ENABLE_SPDIF
       /*if((dacstate->spdif_enable) && (card->spout)) {
          if( (ret =  Nvaudio_mmapwrite(vma,card->spout,2)) != 0)
              goto out;
       }*/
       /* Disable the Spdif right now */
       dacstate->spdif_enable = 0;
#endif
       ret = Nvaudio_mmapwrite(vma,card->wavout,0);
       /* set the samplerate variable now as we donot support spdif with mmap */
       Nvaudio_set_dac_rate(dacstate,dacstate->rate);
       /* set the speaker to stereo mode now as we donot support
       software SRC & downmix to remapped data */
       Nvaudio_SetSpkrRegister(card);

    }
#ifdef NV_DEBUG_NORMAL
    printk("Nvaudio: mmap'ed %ld bytes of data space\n", size);
#endif
out:
    unlock_kernel();
    return ret;
}

/**********************************************************************
*  Function that supports the fragment size for hw and user buffer
*  Avoiding smaller buffer as it glitches with the games.
*  Need to verify whether it will create problem with other apps
**********************************************************************/
int Nvaudio_setfragment(struct dmabuf* dmabuf, int val)
{
   dmabuf->ossfragsize = 1 << (val & 0xffff);
   dmabuf->ossmaxfrags = (val >> 16) & 0xffff;

   if (!dmabuf->ossfragsize || !dmabuf->ossmaxfrags)
        return -EINVAL;
   /*
   * Bound the frag size into our allowed range of 256 - 4096
   */

#ifdef NV_DEBUG_NORMAL
   printk("SNDCTL_DSP_SETFRAGMENT when coming 0x%x, %d, %d\n", val,
          dmabuf->ossfragsize, dmabuf->ossmaxfrags);
#endif

     //if (dmabuf->ossfragsize < 256)
      // dmabuf->ossfragsize = 256;
     //small buffersize giving glitch - so setting to high value
     if (dmabuf->ossfragsize < 1024)
        dmabuf->ossfragsize = 1024;
     else if (dmabuf->ossfragsize > 4096)
          dmabuf->ossfragsize = 4096;
      /*
         * The numfrags could be something reasonable, or it could
         * be 0xffff meaning "Give me as much as possible".  So,
         * we check the numfrags * fragsize doesn't exceed our
         * 64k buffer limit, nor is it less than our 8k minimum.
         * If it fails either one of these checks, then adjust the
         * number of fragments, not the size of them.  It's OK if
         * our number of fragments doesn't equal 32 or anything
         * like our hardware based number now since we are using
         * a different frag count for the hardware.  Before we get
         * into this though, bound the maxfrags to avoid overflow
         * issues.  A reasonable bound would be 64k / 256 since our
         * maximum buffer size is 64k and our minimum frag size is
         * 256.  On the other end, our minimum buffer size is 8k and
         * our maximum frag size is 64k, so the lower bound should
         * be 2.
         */

        /*if(dmabuf->ossmaxfrags > 256)
            dmabuf->ossmaxfrags = 256;
        else if (dmabuf->ossmaxfrags < 2)
           dmabuf->ossmaxfrags = 2;*/
        //small fragsize giving glitch - so setting to high value
        if (dmabuf->ossmaxfrags > 64)
           dmabuf->ossmaxfrags = 64;
        else if (dmabuf->ossmaxfrags < 16)
           dmabuf->ossmaxfrags = 16;
    
        val = dmabuf->ossfragsize * dmabuf->ossmaxfrags;

        while (val < 8192) {
            val <<= 1;
            dmabuf->ossmaxfrags <<= 1;
        }

        while (val > 65536) {
            val >>= 1;
            dmabuf->ossmaxfrags >>= 1;
        }

#ifdef NV_DEBUG_NORMAL
        printk("SNDCTL_DSP_SETFRAGMENT 0x%x, %d, %d\n", val,
            dmabuf->ossfragsize, dmabuf->ossmaxfrags);
#endif
        return val;
}

/**********************************************************************
* Nvaudio_dsppost
**********************************************************************/
static void Nvaudio_dsppost(struct Nvaudio_state *state)
{
    unsigned long  flags;
    int val = 0;
     struct dmabuf *dmabuf = &state->dmabuffer;
     spin_lock_irqsave(&state->lock,flags);
     if((dmabuf->swptr % dmabuf->fragsize) != 0) {
         val = dmabuf->fragsize - (dmabuf->swptr % dmabuf->fragsize);
         dmabuf->swptr += val;
         dmabuf->count += val;
     }
     spin_unlock_irqrestore(&state->lock,flags);

}

/**********************************************************************
* Nvaudio_dsp_reset
**********************************************************************/
void Nvaudio_dsp_reset(struct Nvaudio_state *state,unsigned rec)
{
    unsigned long  flags;
    unsigned long port;
    int channelnum = 0;
    struct dmabuf* dmabuf = &state->dmabuffer;

    spin_lock_irqsave(&state->lock, flags);

    if (state->enable == DAC_RUNNING) {
          __stop_dac(state);
    }
    else if (state->enable == ADC_RUNNING) {
          __stop_adc(state);
    }
    else if(state->enable == SPDIF_RUNNING){
          __stop_spdif(state);
    }
    port = state->card->iobase;

    switch(rec){
        case 0: /* audio out */
         port += PO_BDBAR;
         channelnum = 0;
         break;
    case 1: /* audio in */
         port += PI_BDBAR;
         channelnum = 1;
         break;
    case 2: /* spdif out */
         port += SP_BDBAR;
         channelnum = 2;
         break;
    }
    outb(2, port+OFF_CR);   /* reset DMA machine */
    outl((u32)(state->card->chandma + (channelnum * sizeof(struct Nvaudio_channel))),
            port+OFF_BDBAR);
    spin_unlock_irqrestore(&state->lock, flags);
    synchronize_irq();
    state->ready = 0;
    dmabuf->swptr = dmabuf->hwptr = 0;
    dmabuf->count = dmabuf->total_bytes = 0;

}

/**********************************************************************
* All the OSS ioctls are supported in this function
**********************************************************************/
static int Nvaudio_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
    unsigned long  flags;
    audio_buf_info abinfo;
    count_info     cinfo;
    unsigned int   i_glob_cnt;
    int val = 0, ret;

    struct Nvaudio_state *recstate = NULL;
    struct Nvaudio_state *dacstate = NULL;
    struct Nvaudio_state *spstate  = NULL;
    struct dmabuf *dacdmabuf       = NULL;
    struct dmabuf *recdmabuf       = NULL;
    struct dmabuf *spdmabuf        = NULL;
    struct Nvaudio_card *card      = (struct Nvaudio_card *)file->private_data;
    struct ac97_codec *codec       = card->ac97_codec[0];


    if(file->f_mode & FMODE_WRITE) {

#ifdef NV_DEBUG_NORMAL
        printk("DAC ....  ");
#endif
        dacstate    = card->wavout;
        dacdmabuf   = &dacstate->dmabuffer;
        spstate     = card->spout;
        spdmabuf    = &spstate->dmabuffer;
    }

    if(file->f_mode & FMODE_READ) {

#ifdef NV_DEBUG_NORMAL
        printk("REC .... ");
#endif
        recstate   = card->wavin;
        recdmabuf  = &recstate->dmabuffer;
    }

#ifdef NV_DEBUG_NORMAL
    printk("Nvaudio: Nvaudio_ioctl, arg=0x%x, cmd=", arg ? *(int *)arg : 0);
#endif

    switch (cmd)
    {
        case OSS_GETVERSION:
#ifdef NV_DEBUG_NORMAL
            printk("OSS_GETVERSION\n");
#endif
            return put_user(SOUND_VERSION, (int *)arg);

        case SNDCTL_DSP_RESET:
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_RESET\n");
#endif
            if(file->f_mode & FMODE_WRITE) {
                Nvaudio_dsp_reset(dacstate,0);
#ifdef ENABLE_SPDIF
                if((dacstate->spdif_enable)&&(spstate)) {
                    Nvaudio_dsp_reset(spstate,2);
                }
#endif
            }
            if(file->f_mode & FMODE_READ) {
                Nvaudio_dsp_reset(recstate,1);
            }
            return 0;

        case SNDCTL_DSP_SYNC:
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_SYNC\n");
#endif
            if(file->f_mode & FMODE_WRITE) {
                if (dacstate->enable != DAC_RUNNING || file->f_flags & O_NONBLOCK)
                    return 0;

                if((val = drain_dac(dacstate, 1)))
                    return val;

                dacdmabuf->total_bytes = 0;
                spdmabuf->total_bytes  = 0;
            }
            return 0;

    case SNDCTL_DSP_SPEED: /* set smaple rate */

#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_SPEED\n");
#endif
            if (get_user(val, (int *)arg))
                return -EFAULT;
            if (val >= 0) {
                if (file->f_mode & FMODE_WRITE) {
#ifdef NV_DEBUG_NORMAL
                    printk("Stop DAC Getting called from dsp_speed \n");
#endif
                    stop_dac(dacstate);
                    dacstate->ready = 0;
                    spin_lock_irqsave(&dacstate->lock, flags);
                    val = Nvaudio_set_dac_rate(dacstate, val);
                    spin_unlock_irqrestore(&dacstate->lock, flags);
#ifdef ENABLE_SPDIF
                    if((dacstate->spdif_enable)&&(spstate)) {
                        stop_spdif(spstate);
                        spstate->ready = 0;
                        spin_lock_irqsave(&spstate->lock, flags);
                        val = Nvaudio_set_spdif_rate(spstate, val);
                        spin_unlock_irqrestore(&spstate->lock, flags);
                    }
#endif
                }
                if (file->f_mode & FMODE_READ) {
                    stop_adc(recstate);
#ifdef NV_DEBUG_NORMAL
                    printk("Setting ADC rate as %d \n" , val);
#endif
                    recstate->ready = 0;
                    spin_lock_irqsave(&recstate->lock, flags);
                    val = Nvaudio_set_adc_rate(recstate, val);
                    spin_unlock_irqrestore(&recstate->lock, flags);
                }
            }
            return put_user(val, (int *)arg);

        case SNDCTL_DSP_STEREO: /* set stereo or mono channel */
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_STEREO\n");
#endif
            if(file->f_mode & FMODE_WRITE) {
               if (dacstate->enable & DAC_RUNNING) {
                   stop_dac(dacstate);
               }
#ifdef ENABLE_SPDIF
               if((dacstate->spdif_enable)&&(spstate)){
                   if(spstate->enable & SPDIF_RUNNING)
                       stop_spdif(spstate);
               }
#endif
            }
            if(file->f_mode & FMODE_READ){
               if (recstate->enable & ADC_RUNNING) {
                  stop_adc(recstate);
               }
            }
            return put_user(1, (int *)arg);

        case SNDCTL_DSP_GETBLKSIZE:

            if (file->f_mode & FMODE_WRITE) {
                if (!dacstate->ready && (val = prog_dmabuf(dacstate, 0)))
                    return val;
                val = dacdmabuf->userfragsize;
#ifdef ENABLE_SPDIF
               if((dacstate->spdif_enable)&&(spstate)){
                  if (!spstate->ready && (val = prog_dmabuf(spstate, 2)))
                    return val;
                  val = dacdmabuf->userfragsize;
               }
#endif
            }
            if (file->f_mode & FMODE_READ) {
                if (!recstate->ready && (val = prog_dmabuf(recstate, 1)))
                    return val;
                val = recdmabuf->userfragsize;
            }
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_GETBLKSIZE %d\n", val);
#endif
            return put_user(val, (int *)arg);

        case SNDCTL_DSP_GETFMTS: /* Returns a mask of supported sample format*/
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_GETFMTS\n");
#endif
            return put_user(card->waveformat, (int *)arg);

        case SNDCTL_DSP_SETFMT: /* Select sample format */
            get_user(val,(int*)arg);
            card->ac3passthrough = 0;
            card->waveformat     = AFMT_S16_LE;
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_SETFMT\n");
#endif
            if(val & AFMT_AC3) {
                card->ac3passthrough = 1;
                card->waveformat     = AFMT_AC3;
            }
            return put_user(card->waveformat, (int *)arg);

        case SNDCTL_DSP_CHANNELS:
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_CHANNELS \n");
#endif
            if (get_user(val, (int *)arg))
                return -EFAULT;

            if (val > 0) {
                if(file->f_mode & FMODE_READ){
                    if (recstate->enable & ADC_RUNNING) {
                       stop_adc(recstate);
                    }
                }
                
                if(file->f_mode & FMODE_WRITE) {
#ifdef ENABLE_SPDIF
                    if((dacstate->spdif_enable)&&(spstate)) {
                        if(spstate->enable & SPDIF_RUNNING)
                            stop_spdif(spstate);
                    }
#endif
                    if (dacstate->enable & DAC_RUNNING) {
                         stop_dac(dacstate);
                    }
                }
            } else {
                return put_user(card->channels, (int *)arg);
            }
            /* Multi-channel support was added with ICH2. Bits in */
            /* Global Status and Global Control register are now  */
            /* used to indicate this.                             */

            i_glob_cnt = inl(card->iobase + GLOB_CNT);

#ifdef NV_DEBUG_NORMAL
            printk("GLOB_CNT %x and supported %x \n",i_glob_cnt, card->channels);
#endif

            /* Current # of channels enabled */
            if ( i_glob_cnt & 0x0100000 )
                ret = 4;
            else if ( i_glob_cnt & 0x0200000 )
                ret = 6;
            else
                ret = 2;

            switch ( val ) {
                case 2: /* 2 channels is always supported */
                    /*Set the channels getting used; */
                    card->numchannels = val;

                    /* Check the Spkr configuration to set the Register*/
                    Nvaudio_SetSpkrRegister(card);

                    /* Do we need to change mixer settings????  */
                    break;
                case 4: /* Supported on some chipsets, better check first */
                    if ( card->channels >= 4 ) {

                        /*Set the channels getting used; */
                        card->numchannels = val;

                        /* Check the Spkr configuration to set the Register*/
                        Nvaudio_SetSpkrRegister(card);

                        /* Read the Register and set as well*/
                        //Nvaudio_MultiChannel_volume(card,val);
                        
#ifdef NV_DEBUG_NORMAL
                        printk("pcm 0x%x, master 0x%x, sur 0x%x, reg64 0x%x  \n",
                                Nvaudio_ac97_get(codec,AC97_PCMOUT_VOL),
                                Nvaudio_ac97_get(codec,AC97_MASTER_VOL_STEREO),
                                Nvaudio_ac97_get(codec,AC97_SURROUND_MASTER),
                                Nvaudio_ac97_get(codec,0x64));
#endif

                    } else {
                        val = ret;
                    }
                    break;
                case 6: /* Supported on some chipsets, better check first */
                    if ( card->channels >= 6 ) {

                        /*Set the channels getting used; */
                        card->numchannels = val;

                        /* Check the Spkr configuration to set the Register*/
                        Nvaudio_SetSpkrRegister(card);

                        /* Read the Register and set as well*/
                        //Nvaudio_MultiChannel_volume(card,val);
                        
#ifdef NV_DEBUG_NORMAL
                             printk(" channel 6 pcm 0x%x, master 0x%x, sur 0x%x , center 0x%x, reg64 0x%x , reg66 0x%x \n",
                                Nvaudio_ac97_get(codec,AC97_PCMOUT_VOL),
                                Nvaudio_ac97_get(codec,AC97_MASTER_VOL_STEREO),
                                Nvaudio_ac97_get(codec,AC97_SURROUND_MASTER),
                                Nvaudio_ac97_get(codec,AC97_CENTER_LFE_MASTER),
                                Nvaudio_ac97_get(codec,0x64),
                                Nvaudio_ac97_get(codec,0x66));
#endif

                   
                    } else {
                        val = ret;
                    }
                    break;
                default: /* nothing else is ever supported by the chipset */
                    val = ret;
                    break;
            }

            /*Set the channels getting used; */
            card->numchannels = val;
            return put_user(val, (int *)arg);

        case SNDCTL_DSP_POST: /* the user has sent all data and is notifying us */
            /* we update the swptr to the end of the last sg segment then return */
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_POST\n");
#endif
            if(file->f_mode & FMODE_WRITE) {
                if(!dacstate->ready || (dacstate->enable != DAC_RUNNING))
                    return 0;
                Nvaudio_dsppost(dacstate);

#ifdef ENABLE_SPDIF
                if((dacstate->spdif_enable)&&(spstate)) {
                    if(!spstate->ready || (spstate->enable != DAC_RUNNING))
                        return 0;
                    Nvaudio_dsppost(spstate);
                }
#endif
            }
            return 0;

        case SNDCTL_DSP_SUBDIVIDE:
            if (get_user(val, (int *)arg))
                return -EFAULT;
            if (val != 1 && val != 2 && val != 4)
                return -EINVAL;
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_SUBDIVIDE %d\n", val);
#endif

            if(file->f_mode & FMODE_WRITE){
                if (dacdmabuf->subdivision)
                    return -EINVAL;
                dacdmabuf->subdivision = val;
                dacstate->ready = 0;

#ifdef ENABLE_SPDIF
                if((dacstate->spdif_enable)&&(spstate)) {
                    if (spdmabuf->subdivision)
                        return -EINVAL;
                    spdmabuf->subdivision = val;
                    spstate->ready = 0;
                }
#endif
            }
            if(file->f_mode & FMODE_READ){
                if (recdmabuf->subdivision)
                    return -EINVAL;
                recdmabuf->subdivision = val;
                recstate->ready = 0;
            }
            return 0;

        case SNDCTL_DSP_SETFRAGMENT:
            if(get_user(val, (int *)arg))
                return -EFAULT;
            if(val == 0) return -EIO;
            if(file->f_mode & FMODE_WRITE){
                val = Nvaudio_setfragment(dacdmabuf,val);
                dacstate->ready = 0;
                if(val == 0 )return -EINVAL;

#ifdef ENABLE_SPDIF
                if((dacstate->spdif_enable)&&(spstate)) {
                    get_user(val, (int *)arg);
                    val = Nvaudio_setfragment(spdmabuf,val);
                    spstate->ready = 0;
                    if(val == 0 )return -EINVAL;
                }
#endif
            }
            if(file->f_mode & FMODE_READ){
               get_user(val, (int *)arg);
               val = Nvaudio_setfragment(recdmabuf,val);
               recstate->ready = 0;
            }
            if(val == 0 )return -EINVAL;
                return 0;

        case SNDCTL_DSP_GETOSPACE:
            if (!(file->f_mode & FMODE_WRITE))
                return -EINVAL;
            if (!dacstate->ready && (val = prog_dmabuf(dacstate, 0)) != 0)
                return val;
            
            spin_lock_irqsave(&dacstate->lock, flags);
            Nvaudio_update_ptr(dacstate);
            abinfo.fragsize     = dacdmabuf->userfragsize;
            abinfo.fragstotal   = dacdmabuf->userfrags;
            if (dacstate->mapped)
                abinfo.bytes    = dacdmabuf->dmasize;
            else
                abinfo.bytes    = Nvaudio_get_free_write_space(dacstate);
            abinfo.fragments    = abinfo.bytes / dacdmabuf->userfragsize;
            spin_unlock_irqrestore(&dacstate->lock, flags);

#ifdef ENABLE_SPDIF
            if((dacstate->spdif_enable)&&(spstate)) {
                if (!spstate->ready && (val = prog_dmabuf(spstate, 2)) != 0){
                     /* Do we need to return - no */
                }
                if(!card->analogstate && card->digitalstate)
                {
                    spin_lock_irqsave(&spstate->lock, flags);
                    Nvaudio_update_ptr(spstate);
                    abinfo.fragsize     = spdmabuf->userfragsize;
                    abinfo.fragstotal   = spdmabuf->userfrags;
                    if (dacstate->mapped)
                        abinfo.bytes    = spdmabuf->dmasize;
                    else
                        abinfo.bytes    = Nvaudio_get_free_write_space(spstate);
                    abinfo.fragments    = abinfo.bytes / spdmabuf->userfragsize;
                    spin_unlock_irqrestore(&spstate->lock, flags);

                }
            }
#endif
#if defined(NV_DEBUG_NORMAL) || defined(DEBUG_MMAP)
            printk("SNDCTL_DSP_GETOSPACE %d, %d, %d, %d\n", abinfo.bytes,
                 abinfo.fragsize, abinfo.fragments, abinfo.fragstotal);
#endif
            return copy_to_user((void *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0;

        case SNDCTL_DSP_GETOPTR:
            if (!(file->f_mode & FMODE_WRITE))
                return -EINVAL;
            if (!dacstate->ready && (val = prog_dmabuf(dacstate, 0)) != 0)
                return val;
            spin_lock_irqsave(&dacstate->lock, flags);
            val = Nvaudio_get_free_write_space(dacstate);
            cinfo.bytes     = dacdmabuf->total_bytes;
            cinfo.ptr       = dacdmabuf->hwptr;
            cinfo.blocks    = val / dacdmabuf->userfragsize;
            if (dacstate->mapped && (dacstate->trigger & PCM_ENABLE_OUTPUT)) {
                dacdmabuf->count += val;
                dacdmabuf->swptr = (dacdmabuf->swptr + val) % dacdmabuf->dmasize;
                __Nvaudio_update_lvi(dacstate);
            }
            spin_unlock_irqrestore(&dacstate->lock, flags);
#ifdef ENABLE_SPDIF
            if((dacstate->spdif_enable)&&(spstate)) {
                if (!spstate->ready && (val = prog_dmabuf(spstate, 2)) != 0)  {
                    /* Do we need to return - no */
                }else {
                    spin_lock_irqsave(&spstate->lock, flags);
                    val = Nvaudio_get_free_spwrite_space(spstate);
                    if (spstate->mapped && (spstate->trigger & PCM_ENABLE_OUTPUT)) {
                        spdmabuf->count += val;
                        spdmabuf->swptr = (spdmabuf->swptr + val) % spdmabuf->dmasize;
                        __Nvaudio_update_splvi(spstate);
                    }
                    spin_unlock_irqrestore(&spstate->lock, flags);
                }
            }
#endif
#if defined(NV_DEBUG_NORMAL) || defined(DEBUG_MMAP)
            printk("SNDCTL_DSP_GETOPTR %d, %d, %d, %d\n", cinfo.bytes,
                cinfo.blocks, cinfo.ptr, dacdmabuf->count);
#endif
            return copy_to_user((void *)arg, &cinfo, sizeof(cinfo));

        case SNDCTL_DSP_GETISPACE:
            if (!(file->f_mode & FMODE_READ))
                return -EINVAL;
            if (!recstate->ready && (val = prog_dmabuf(recstate, 1)) != 0)
                return val;
            spin_lock_irqsave(&recstate->lock, flags);
            abinfo.bytes        = Nvaudio_get_available_read_data(recstate);
            abinfo.fragsize     = recdmabuf->userfragsize;
            abinfo.fragstotal   = recdmabuf->userfrags;
            abinfo.fragments    = abinfo.bytes / recdmabuf->userfragsize;
            spin_unlock_irqrestore(&recstate->lock, flags);
#if defined(NV_DEBUG_NORMAL) || defined(DEBUG_MMAP)
            printk("SNDCTL_DSP_GETISPACE %d, %d, %d, %d\n", abinfo.bytes,
                abinfo.fragsize, abinfo.fragments, abinfo.fragstotal);
#endif
            return copy_to_user((void *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0;

        case SNDCTL_DSP_GETIPTR:
            if (!(file->f_mode & FMODE_READ))
                return -EINVAL;
            if (!recstate->ready && (val = prog_dmabuf(recstate, 1)) != 0)
                return val;
            spin_lock_irqsave(&recstate->lock, flags);
            val             = Nvaudio_get_available_read_data(recstate);
            cinfo.bytes     = recdmabuf->total_bytes;
            cinfo.blocks    = val / recdmabuf->userfragsize;
            cinfo.ptr       = recdmabuf->hwptr;
            if (recstate->mapped && (recstate->trigger & PCM_ENABLE_INPUT)) {
                recdmabuf->count -= val;
                recdmabuf->swptr = (recdmabuf->swptr + val) % recdmabuf->dmasize;
                __Nvaudio_update_reclvi(recstate);
            }
            spin_unlock_irqrestore(&recstate->lock, flags);
#if defined(NV_DEBUG_NORMAL) || defined(DEBUG_MMAP)
            printk("SNDCTL_DSP_GETIPTR %d, %d, %d, %d\n", cinfo.bytes,
                cinfo.blocks, cinfo.ptr, recdmabuf->count);
#endif
            return copy_to_user((void *)arg, &cinfo, sizeof(cinfo));

        case SNDCTL_DSP_NONBLOCK:
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_NONBLOCK\n");
#endif
            file->f_flags |= O_NONBLOCK;
            return 0;

        case SNDCTL_DSP_GETCAPS:
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_GETCAPS\n");
#endif
            return put_user(DSP_CAP_DUPLEX | DSP_CAP_REALTIME | DSP_CAP_TRIGGER|
                            DSP_CAP_MMAP | DSP_CAP_BIND | DSP_CAP_MULTI,(int *)arg);

        case SNDCTL_DSP_GETTRIGGER:
            val = 0;
            if(file->f_mode & FMODE_WRITE) {
               if(dacstate->trigger == PCM_ENABLE_OUTPUT)
                     val |= PCM_ENABLE_OUTPUT;
            }
            if(file->f_mode & FMODE_READ) {
               if(recstate->trigger == PCM_ENABLE_INPUT)
                     val |= PCM_ENABLE_INPUT;
            }
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_GETTRIGGER 0x%x\n", val);
#endif
            return put_user(val, (int *)arg);

        case SNDCTL_DSP_SETTRIGGER:
            if (get_user(val, (int *)arg))
                return -EFAULT;

#if defined(NV_DEBUG_NORMAL) || defined(DEBUG_MMAP)
            printk("SNDCTL_DSP_SETTRIGGER 0x%x\n", val);
#endif
            if(file->f_mode & FMODE_WRITE) {
                if( !(val & PCM_ENABLE_OUTPUT) && dacstate->enable == DAC_RUNNING) {
                    stop_dac(dacstate);
                }

                if((val & PCM_ENABLE_OUTPUT) && !(dacstate->enable & DAC_RUNNING)) {
                    dacstate->trigger = PCM_ENABLE_OUTPUT;

                    if (!dacstate->ready && (ret = prog_dmabuf(dacstate, 0)))
                        return ret;
                    if (dacstate->mapped) {
                        spin_lock_irqsave(&dacstate->lock, flags);
                        Nvaudio_update_ptr(dacstate);
                        dacdmabuf->count = 0;
                        dacdmabuf->swptr = dacdmabuf->hwptr;
                        dacdmabuf->count = Nvaudio_get_free_write_space(dacstate);
                        dacdmabuf->swptr = (dacdmabuf->swptr + dacdmabuf->count) % dacdmabuf->dmasize;
                        __Nvaudio_update_lvi(dacstate);
                        spin_unlock_irqrestore(&dacstate->lock, flags);
                    } else {
#ifdef NV_DEBUG_NORMAL
                        printk("Start DAC Getting called from DSP_SETTRIGGER \n");
#endif
                        start_dac(dacstate);
                    }
#ifdef ENABLE_SPDIF
                    if((dacstate->spdif_enable)&&(spstate)) {
                        spstate->trigger = PCM_ENABLE_OUTPUT;

                        if (!spstate->ready && (ret = prog_dmabuf(spstate, 2))) {
                            /* Do we need to return - no */
                        }else{
                            if (spstate->mapped) {
                                spin_lock_irqsave(&spstate->lock, flags);
                                Nvaudio_update_spptr(spstate);
                                spdmabuf->count = 0;
                                spdmabuf->swptr = spdmabuf->hwptr;
                                spdmabuf->count = Nvaudio_get_free_spwrite_space(spstate);
                                spdmabuf->swptr = (spdmabuf->swptr + spdmabuf->count) % spdmabuf->dmasize;
                                __Nvaudio_update_splvi(spstate);
                                spin_unlock_irqrestore(&spstate->lock, flags);
                            } else {
                                start_spdif(spstate);
                            }
                        }

                    }
#endif
                }
            }
            get_user(val, (int *)arg);
            if(file->f_mode & FMODE_READ) {

                if( !(val & PCM_ENABLE_INPUT) && recstate->enable == ADC_RUNNING) {
                      stop_adc(recstate);
                }
                if((val & PCM_ENABLE_INPUT) && !(recstate->enable & ADC_RUNNING)) {
                    recstate->trigger = PCM_ENABLE_INPUT;

                    if (!recstate->ready && (ret = prog_dmabuf(recstate, 1)))
                        return ret;
                    if (recstate->mapped) {
                        spin_lock_irqsave(&recstate->lock, flags);
                        Nvaudio_update_recptr(recstate);
                        recdmabuf->swptr = recdmabuf->hwptr;
                        recdmabuf->count = 0;
                        spin_unlock_irqrestore(&recstate->lock, flags);
                    }
                    Nvaudio_update_reclvi(recstate);
                    start_adc(recstate);
               }
            }
            return 0;

        case SNDCTL_DSP_SETDUPLEX:
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_SETDUPLEX\n");
#endif
            return -EINVAL;

        case SNDCTL_DSP_GETODELAY:
            if (!(file->f_mode & FMODE_WRITE))
                return -EINVAL;
            spin_lock_irqsave(&dacstate->lock, flags);
            Nvaudio_update_ptr(dacstate);
            val = dacdmabuf->count;
            spin_unlock_irqrestore(&dacstate->lock, flags);
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_GETODELAY %d\n", dacdmabuf->count);
#endif
            return put_user(val, (int *)arg);

        case SOUND_PCM_READ_RATE:
            val = 0 ;
            if(file->f_mode & FMODE_READ) {
                val = recstate->rate;
            }
            else if(file->f_mode & FMODE_WRITE) {
                val = dacstate->rate;
            }
#ifdef NV_DEBUG_NORMAL
            printk("SOUND_PCM_READ_RATE %d\n", val);
#endif
            return put_user(val, (int *)arg);

        case SOUND_PCM_READ_CHANNELS:
#ifdef NV_DEBUG_NORMAL
            printk("SOUND_PCM_READ_CHANNELS\n");
#endif
            return put_user(2, (int *)arg);

        case SOUND_PCM_READ_BITS:
#ifdef NV_DEBUG_NORMAL
            printk("SOUND_PCM_READ_BITS\n");
#endif
            return put_user(card->waveformat, (int *)arg);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,9)

        case SNDCTL_DSP_SETSPDIF: /* Set S/PDIF Control register */
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_SETSPDIF\n");
#endif
            if (get_user(val, (int *)arg))
                return -EFAULT;

            /* Check to make sure the codec supports S/PDIF transmitter */

            if((card->ac97_features & 4)) {
                /* mask out the transmitter speed bits so the user can't set them */
                val &= ~0x3000;

                /* Add the current transmitter speed bits to the passed value */
                ret = Nvaudio_ac97_get(codec, AC97_SPDIF_CONTROL);
                val |= (ret & 0x3000);

                Nvaudio_ac97_set(codec, AC97_SPDIF_CONTROL, val);
                if(Nvaudio_ac97_get(codec, AC97_SPDIF_CONTROL) != val ) {
                    printk(KERN_ERR "Nvaudio: Unable to set S/PDIF configuration to 0x%04x.\n", val);
                    return -EFAULT;
                }
            }
#ifdef NV_DEBUG_NORMAL
            else
                printk(KERN_WARNING "Nvaudio: S/PDIF transmitter not avalible.\n");
#endif
            return put_user(val, (int *)arg);

        case SNDCTL_DSP_GETSPDIF: /* Get S/PDIF Control register */
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_GETSPDIF\n");
#endif
            if (get_user(val, (int *)arg))
                return -EFAULT;

            /* Check to make sure the codec supports S/PDIF transmitter */

            if(!(card->ac97_features & 4)) {
#ifdef NV_DEBUG_NORMAL
                printk(KERN_WARNING "Nvaudio: S/PDIF transmitter not avalible.\n");
#endif
                val = 0;
            } else {
                val = Nvaudio_ac97_get(codec, AC97_SPDIF_CONTROL);
            }
            //return put_user((val & 0xcfff), (int *)arg);
            return put_user(val, (int *)arg);
#endif

        case SNDCTL_DSP_GETCHANNELMASK:
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_DSP_GETCHANNELMASK\n");
#endif
            if (get_user(val, (int *)arg))
                return -EFAULT;

            /* Based on AC'97 DAC support, not ICH hardware */
            val = DSP_BIND_FRONT;
            if ( card->ac97_features & 0x0004 )
                val |= DSP_BIND_SPDIF;

            if ( card->ac97_features & 0x0080 )
                val |= DSP_BIND_SURR;
            if ( card->ac97_features & 0x0140 )
                val |= DSP_BIND_CENTER_LFE;

            return put_user(val, (int *)arg);

        case SNDCTL_DSP_BIND_CHANNEL:
#ifdef NV_DEBUG_CHANNELS
            printk("SNDCTL_DSP_BIND_CHANNEL\n");
#endif
            if (get_user(val, (int *)arg))
                return -EFAULT;
            if ( val == DSP_BIND_QUERY ) {
                val = DSP_BIND_FRONT; /* Always report this as being enabled */
                if ( card->ac97_status & SPDIF_ON )
                    val |= DSP_BIND_SPDIF;
                else {
                    if ( card->ac97_status & SURR_ON )
                        val |= DSP_BIND_SURR;
                    if ( card->ac97_status & CENTER_LFE_ON )
                        val |= DSP_BIND_CENTER_LFE;
                }
            } else {  /* Not a query, set it */
                if (!(file->f_mode & FMODE_WRITE))
                    return -EINVAL;

                if ( dacstate->enable == DAC_RUNNING ) {
                    stop_dac(dacstate);
                }

                if ( val & DSP_BIND_SPDIF ) {  /* Turn on SPDIF */
                    /*  Ok, this should probably define what slots
                     *  to use. For now, we'll only set it to the
                     *  defaults:
                     *
                     *   non multichannel codec maps to slots 3&4
                     *   2 channel codec maps to slots 7&8
                     *   4 channel codec maps to slots 6&9
                     *   6 channel codec maps to slots 10&11
                     *
                     *  there should be some way for the app to
                     *  select the slot assignment.
                     */

                   // Nvaudio_set_spdif_output ( state, AC97_EA_SPSA_3_4, state->rate );
                    if ( !(card->ac97_status & SPDIF_ON) )
                        val &= ~DSP_BIND_SPDIF;
                } else {
                    int mask = 0;
                    int channels;

                    /* Turn off S/PDIF if it was on */
                    if (card->ac97_status & SPDIF_ON )
//                        Nvaudio_set_spdif_output ( state, -1, 0 );

                    mask = val & (DSP_BIND_FRONT | DSP_BIND_SURR | DSP_BIND_CENTER_LFE);
                    switch (mask) {
                        case DSP_BIND_FRONT:
                            channels = 2;
                            break;
                        case DSP_BIND_FRONT|DSP_BIND_SURR:
                            channels = 4;
                            break;
                        case DSP_BIND_FRONT|DSP_BIND_SURR|DSP_BIND_CENTER_LFE:
                            channels = 6;
                            break;
                        default:
                            val = DSP_BIND_FRONT;
                            channels = 2;
                            break;
                    }
//                    Nvaudio_set_dac_channels ( state, channels );

                    /* check that they really got turned on */
                    if ( !card->ac97_status & SURR_ON )
                        val &= ~DSP_BIND_SURR;
                    if ( !card->ac97_status & CENTER_LFE_ON )
                        val &= ~DSP_BIND_CENTER_LFE;
                }
            }
            return put_user(val, (int *)arg);

        case SNDCTL_DSP_MAPINBUF:
        case SNDCTL_DSP_MAPOUTBUF:
        case SNDCTL_DSP_SETSYNCRO:
        case SOUND_PCM_WRITE_FILTER:
        case SOUND_PCM_READ_FILTER:
        default:
#ifdef NV_DEBUG_NORMAL
            printk("SNDCTL_* -EINVAL\n");
#endif
            return -EINVAL;
        }
        return -EINVAL;
}

/**********************************************************************
* Function to support the open calls
**********************************************************************/
static int Nvaudio_open(struct inode *inode, struct file *file)
{
    int i  = 0;
    struct Nvaudio_state *state = NULL;
    struct dmabuf *dmabuf = NULL;
    struct Nvaudio_card  *card = devs;

    /* find an avaiable virtual channel (instance of /dev/dsp) */
    while (card != NULL) {
        /*
         * If we are initializing and then fail, card could go
         * away unuexpectedly while we are in the for() loop.
         * So, check for card on each iteration before we check
         * for card->initializing to avoid a possible oops.
         * This usually only matters for times when the driver is
         * autoloaded by kmod.
         */
        for (i = 0; i < 50 && card && card->initializing; i++) {
            set_current_state(TASK_UNINTERRUPTIBLE);
            schedule_timeout(HZ/20);
        }

        if(card && !card->initializing ){
#ifdef NV_DEBUG_NORMAL
            printk("Nvaudio_open without card init \n");
#endif
            /* allocate hardware channels */
            if(file->f_mode & FMODE_READ) {
               // Assign state 0 for ADC
                if(card->wavin) {
                    return -EBUSY;
                }

                card->wavin = (struct Nvaudio_state *)
                    kmalloc(sizeof(struct Nvaudio_state), GFP_KERNEL);

                if (card->wavin == NULL) {
                    return -ENOMEM;
                }

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

                state->card = card;
                init_waitqueue_head(&dmabuf->wait);
                //init_MUTEX(&state->open_sem);

                state->trigger = 0;
                state->trigger = PCM_ENABLE_INPUT;
                Nvaudio_set_adc_rate(state, 48000);
                /* set default sample format. According to OSS Programmer's Guide  /dev/dsp
                should be default to unsigned 8-bits, mono, with sample rate 8kHz and
                /dev/dspW will accept 16-bits sample, but we don't support those so we
                set it immediately to stereo and 16bit, which is all we do support */
                state->fmt |= NVAUDIO_FMT_16BIT | NVAUDIO_FMT_STEREO;
                dmabuf->ossfragsize  = 0;
                dmabuf->ossmaxfrags  = 0;
                dmabuf->subdivision  = 0;
                state->open_mode    |= file->f_mode & (FMODE_READ);
                spin_lock_init(&state->lock);
            }

            if(file->f_mode & FMODE_WRITE) {
                /* support only one stream at a time */ 
                if(card->wavout) {
                    /* some apps opens read and write at same time */
                    /* deallocate rec state if we did so */
                    if((file->f_mode & FMODE_READ) && (card->wavin)) {
                        kfree(card->wavin);
                        card->wavin = NULL;
#ifdef ENABLE_SPDIF
                        if(card->spout) {
                            card->wavout->spdif_enable = 1;
                        }
#endif
                    }
                    return -EBUSY;
                }

                card->wavout = (struct Nvaudio_state *)
                    kmalloc(sizeof(struct Nvaudio_state), GFP_KERNEL);
                
                if (card->wavout == NULL) {
                    /* some apps opens read and write at same time */
                    /* deallocate rec state if we did so */
                    if((file->f_mode & FMODE_READ) && (card->wavin)) {
                        kfree(card->wavin);
                        card->wavin = NULL;
                    }
                    return -ENOMEM;
                }

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

                state->card  = card;
                init_waitqueue_head(&dmabuf->wait);
                //init_MUTEX(&state->open_sem);

                state->trigger = 0;
                state->trigger = PCM_ENABLE_OUTPUT;
                Nvaudio_set_dac_rate(state, 48000);

                /* Put the ACLink in 2 channel mode by default */
                i = inl(card->iobase + GLOB_CNT);
                outl(i & 0xffcfffff, card->iobase + GLOB_CNT);

                /* set the channel number to 2 */
                card->numchannels = CHANNEL_STEREO;

                /* set default sample format. According to OSS Programmer's Guide  /dev/dsp
                should be default to unsigned 8-bits, mono, with sample rate 8kHz and
                /dev/dspW will accept 16-bits sample, but we don't support those so we
                set it immediately to stereo and 16bit, which is all we do support */
                state->fmt |= NVAUDIO_FMT_16BIT | NVAUDIO_FMT_STEREO;
                dmabuf->ossfragsize  = 0;
                dmabuf->ossmaxfrags  = 0;
                dmabuf->subdivision  = 0;

                state->open_mode |= file->f_mode & (FMODE_WRITE);
                spin_lock_init(&state->lock);
                state->spdif_enable = 0;
                card->verifyac3     = 0;

#ifdef ENABLE_SPDIF
                if(spdif_status) {
                    if( 0 == Nvaudio_spdifopen(card)) {
                       state->spdif_enable = 1;
                    }
                }
#endif
            }

            file->private_data = card;
        }
        card = card->next;
    }
#ifdef NV_DEBUG_NORMAL
    printk("Nvaudio_open ending \n");
#endif
    return 0;
}

/**********************************************************************
* Function to support the release/close calls
**********************************************************************/
static int Nvaudio_release(struct inode *inode, struct file *file)
{
    struct Nvaudio_state *state    = NULL;
    struct Nvaudio_state *dacstate = NULL;
    struct dmabuf *dmabuf          = NULL;
    unsigned long flags;
    struct Nvaudio_card *card   = (struct Nvaudio_card *)file->private_data;

    lock_kernel();

    /* stop DMA state machine and free DMA buffers/channels */
    if(file->f_mode & FMODE_READ) {
        state  = card->wavin;
        dmabuf = &state->dmabuffer;

        if(state->trigger & PCM_ENABLE_INPUT) {
               stop_adc(state);
        }

        spin_lock_irqsave(&state->lock, flags);
        dealloc_dmabuf(state);
        spin_unlock_irqrestore(&state->lock, flags);
        kfree(state);
        card->wavin = NULL;
    }

    if(file->f_mode & FMODE_WRITE) {
        dacstate  = card->wavout;
        dmabuf = &dacstate->dmabuffer;
        if(dacstate->trigger & PCM_ENABLE_OUTPUT) {
            drain_dac(dacstate, 0);
        }
        spin_lock_irqsave(&dacstate->lock, flags);
        dealloc_dmabuf(dacstate);
        spin_unlock_irqrestore(&dacstate->lock, flags);
        
#ifdef ENABLE_SPDIF
        if(card->spout)
            dacstate->spdif_enable = 1;

        if(dacstate->spdif_enable) {
           state  = card->spout;
           dmabuf = &state->dmabuffer;
           if(state->trigger & PCM_ENABLE_OUTPUT) {
             drain_spdif(state, 0);
           }
           spin_lock_irqsave(&state->lock, flags);
           dealloc_dmabuf(state);
           spin_unlock_irqrestore(&state->lock, flags);
           kfree(state);
           card->spout = NULL;
       }
#endif
        dacstate->mapped = 0;
        /* set the speaker to existing settings */
        Nvaudio_SetSpkrRegister(card);
        kfree(dacstate);
        card->wavout = NULL;

        if(card->outList) {
            Nvaudio_resetnode(card);
        }
    }
    card->ac3passthrough = 0;
    card->waveformat     = AFMT_S16_LE;
    card->verifyac3      = 0;
    unlock_kernel();
    return 0;
}

struct file_operations Nvaudio_fops = {
    owner:      THIS_MODULE,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,9)
    llseek:     no_llseek,
#endif
    read:       Nvaudio_read,
    write:      Nvaudio_write,
    poll:       Nvaudio_poll,
    ioctl:      Nvaudio_ioctl,
    mmap:       Nvaudio_mmap,
    open:       Nvaudio_open,
    release:    Nvaudio_release,
};
