/**********************************************************************
*
*  nvmain.c
*
*  Descripion  - Main code for the driver
*
*  Copyright (c) 2002-2003 NVIDIA Corporation
*
***********************************************************************
*/

#include <linux/module.h>
#include <linux/version.h>
#include <asm/irq.h>

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

#ifndef PCI_DEVICE_ID_NVIDIA_MCP1_AUDIO
#define PCI_DEVICE_ID_NVIDIA_MCP1_AUDIO 0x01b1
#endif

#ifndef PCI_DEVICE_ID_NVIDIA_MCP2_AUDIO
#define PCI_DEVICE_ID_NVIDIA_MCP2_AUDIO 0x006a
#endif

#ifndef PCI_DEVICE_ID_NVIDIA_MCP3_AUDIO
#define PCI_DEVICE_ID_NVIDIA_MCP3_AUDIO 0x00da
#endif

#ifndef PCI_DEVICE_ID_NVIDIA_CK8S_AUDIO
#define PCI_DEVICE_ID_NVIDIA_CK8S_AUDIO 0x00ea
#endif

/* magic numbers to protect our data structures */
#define NVAUDIO_CARD_MAGIC  0x5072696E /* "Prin" */
#define NVAUDIO_STATE_MAGIC 0x63657373 /* "cess" */
#define NVAUDIO_DMA_MASK    0xffffffff /* DMA buffer mask for pci_alloc_consist */

enum {
    NVIDIA_NFORCE1=0,
    NVIDIA_NFORCE2,
    NVIDIA_NFORCE3,
    NVIDIA_CK8S
};

static char * card_names[] = {
    "NVIDIA nForce1 Audio",
    "NVIDIA nForce2 Audio",
    "NVIDIA Hammer Audio",
    "NVIDIA CK8S Audio"
};

static struct pci_device_id Nvaudio_pci_tbl [] __initdata = {
    {PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_MCP1_AUDIO,
     PCI_ANY_ID, PCI_ANY_ID, 0, 0, NVIDIA_NFORCE1},
    {PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_MCP2_AUDIO,
     PCI_ANY_ID, PCI_ANY_ID, 0, 0, NVIDIA_NFORCE2},
    {PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_MCP3_AUDIO,
     PCI_ANY_ID, PCI_ANY_ID, 0, 0, NVIDIA_NFORCE3},
    {PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_CK8S_AUDIO,
     PCI_ANY_ID, PCI_ANY_ID, 0, 0, NVIDIA_CK8S},
    {0,}
};

MODULE_DEVICE_TABLE (pci, Nvaudio_pci_tbl);

#ifdef CONFIG_PM
#define PM_SUSPENDED(card) (card->pm_suspended)
#else
#define PM_SUSPENDED(card) (0)
#endif

extern struct file_operations Nvaudio_fops;
extern struct file_operations Nvaudio_mixer_fops;

struct Nvaudio_card *devs = NULL;

int strict_clocking = 0;
unsigned int clocking = 0;
unsigned int spdif_status = 1;

static int mastervolleft  = 0;
static int pcmvolleft     = 0;
static int mastervolright = 0;
static int pcmvolright    = 0;

/**********************************************************************
* OSS /dev/mixer file operation methods 
**********************************************************************/
int Nvaudio_open_mixdev(struct inode *inode, struct file *file)
{
    int i;
    int minor = MINOR(inode->i_rdev);
    struct Nvaudio_card *card = devs;

#ifdef NV_DEBUG_NORMAL
    printk("Nvaudio: Nvaudio_open_mixdev \n");
#endif

    for (card = devs; card != NULL; card = card->next) {

        for (i = 0; i < 50 && card && card->initializing; i++) {
            set_current_state(TASK_UNINTERRUPTIBLE);
            schedule_timeout(HZ/20);
        }

        for (i = 0; i < NR_AC97 && card && !card->initializing; i++)
            if (card->ac97_codec[i] != NULL &&
                card->ac97_codec[i]->dev_mixer == minor) {
                file->private_data = card;
                return 0;
            }
    }
    return -ENODEV;
}

/**********************************************************************
* Function to do all private ioctls calls 
**********************************************************************/
static int Nvaudio_private_ioctl(struct Nvaudio_card* card, unsigned int cmd, unsigned long arg)
{
    int ret = 0;
    u32 regIndex = 0;
    u32 regValue = 0;
    int mute     = 0;
    struct nv_ioctl *nvctl;
    //struct ac97_codec *codec       = card->ac97_codec[0];

#ifdef NV_DEBUG_NORMAL
    printk("Nvaudio: Nvaudio_private_ioctl \n");
#endif
    
    switch(cmd) {

      case SOUND_MIXER_PRIVATE1:
        nvctl = (struct nv_ioctl *) kmalloc( sizeof(struct nv_ioctl),GFP_KERNEL);
        if(!nvctl) return -ENOMEM;
        if(copy_from_user(nvctl,(void*) arg, sizeof(struct nv_ioctl))) {
             kfree(nvctl);
             return -EFAULT;
        }

        regIndex = nvctl->val[0];
        regValue = nvctl->val[1];

        switch(nvctl->cmd)   {

            case NV_AC97CODEC_WRITE:
               outw(regValue,card->ac97base + regIndex);
#ifdef NV_DEBUG_NORMAL
               printk("NV_CODEC_WRITE with val %x\n",regValue);
#endif
               break;

            case NV_AC97CODEC_READ:
               nvctl->val[1] = inw(card->ac97base + regIndex);
#ifdef NV_DEBUG_NORMAL
               printk("NV_CODEC_READ with val %lx\n",nvctl->val[1]);
#endif
               if( copy_to_user((void *)arg, nvctl, sizeof(struct nv_ioctl)))
                   ret = -EINVAL;
               break;

            case NV_AC97CON_WRITE:
#ifdef NV_DEBUG_NORMAL
               printk("NV_CON_WRITE with val %x\n",regValue);
#endif
               outw(regValue,card->iobase + regIndex);
               break;

            case NV_AC97CON_READ:
               nvctl->val[1] = inw(card->iobase + regIndex);
#ifdef NV_DEBUG_NORMAL
               printk("NV_CON_READ with val %lx\n",nvctl->val[1]);
#endif
               if( copy_to_user((void *)arg, nvctl, sizeof(struct nv_ioctl)))
                  ret = -EINVAL;
               break;
            case NV_SET_SPKRSELECT:
                card->spkrselect = nvctl->val[1];
                /* set the Global cnt regsiter as well  */
                Nvaudio_SetSpkrRegister(card);
                break;

            case NV_GET_SPKRSELECT:
                nvctl->val[1] = card->spkrselect;
                if( copy_to_user((void *)arg, nvctl, sizeof(struct nv_ioctl)))
                  ret = -EINVAL;
                break;

            case NV_SET_ANALOGOUT:
                ret = Nvaudio_setbusmaster(card,ANALOG_OUT,regValue);
                break;

            case NV_GET_ANALOGOUT:
                nvctl->val[1] = card->analogstate;
                if( copy_to_user((void *)arg, nvctl, sizeof(struct nv_ioctl)))
                  ret = -EINVAL;
                break;

            case NV_SET_DIGITALOUT:
                if(spdif_status) {
                    ret = Nvaudio_setbusmaster(card,DIGITAL_OUT,regValue);
                }else {
                    ret = -EINVAL;
                }
                break;

            case NV_GET_DIGITALOUT:
                nvctl->val[1] = card->digitalstate;
                if( copy_to_user((void *)arg, nvctl, sizeof(struct nv_ioctl)))
                  ret = -EINVAL;
                break;

            case NV_GET_ONOFF:
                switch(regIndex) {
                    case SWAP_LINEIN:
                    case SWAP_MICIN:
                        nvctl->val[1] = Nvaudio_getswapinfo(card,regIndex);
                        break;
                    default:
                        break;
                }
                if( copy_to_user((void *)arg, nvctl, sizeof(struct nv_ioctl)))
                    ret = -EINVAL;

                break;

            case NV_SET_ONOFF:
                switch(regIndex) {
                    case SWAP_LINEIN:
                    case SWAP_MICIN:
                        Nvaudio_setswapinfo(card,regIndex,regValue);
                        break;
                }
                break;

            case NV_SET_MICBOOST:
                mdelay(20); /* wait to register to update */
                regValue = inw(card->ac97base + AC97_MIC_VOL) & ~(AC97_MICBOOST_MASK);
                regValue |= (nvctl->val[1] << 6);
                outw(regValue,card->ac97base + AC97_MIC_VOL); 
                card->micboost   = nvctl->val[1];
                break;

            case NV_GET_MICBOOST:
                regValue      = inw(card->ac97base + AC97_MIC_VOL) & AC97_MICBOOST_MASK;
                if(card->micboost){
                    regValue = inw(card->ac97base + AC97_MIC_VOL) | AC97_MICBOOST_MASK;
                    outw(regValue,card->ac97base + AC97_MIC_VOL); 
                    regValue   &= AC97_MICBOOST_MASK;
                }
                nvctl->val[1] = (regValue >> 6);
                if( copy_to_user((void *)arg, nvctl, sizeof(struct nv_ioctl)))
                   ret = -EINVAL;
                break;

            case NV_SET_PREMIXVOL:
                // RegIndex check for position
                if(card->codecType == ADI_CODEC) { 
                    /* center / lfe seemed swapped */
                    if(regIndex == PREMIX_CENTER) {
                        regIndex = PREMIX_SUB;
                    }else if (regIndex == PREMIX_SUB) {
                        regIndex = PREMIX_CENTER;
                    }
                }
                regValue = Nvaudio_setpremix_volume(card,regIndex,regValue);
                break;

            case NV_GET_PREMIXVOL:
                regValue = Nvaudio_getpremix_volume(card,regIndex,&mute);
                nvctl->val[0]  = mute;
                nvctl->val[1]  = regValue;           
                if( copy_to_user((void *)arg, nvctl, sizeof(struct nv_ioctl)))
                   ret = -EINVAL;
                break;
            
            case NV_SET_MIXERMUTE:
                Nvaudio_set_mixermute(card,regIndex,regValue);
                break;

            case NV_GET_MIXERMUTE:
                regValue = Nvaudio_get_mixermute(card,regIndex);
                nvctl->val[1]  = regValue;           
                if( copy_to_user((void *)arg, nvctl, sizeof(struct nv_ioctl)))
                   ret = -EINVAL;
                break;

            case NV_GET_INFO:
                regValue = Nvaudio_getinfo(card,regIndex,nvctl->name);
                nvctl->val[1]  = regValue;   
                if( copy_to_user((void *)arg, nvctl, sizeof(struct nv_ioctl)))
                   ret = -EINVAL;
                break;

            default:
               ret = -EINVAL;
        }
        kfree(nvctl);
        break;

      default:
        ret = -EINVAL;
        break;
    }
    return ret;

}

/**********************************************************************
* mixer ioctls 
**********************************************************************/
int Nvaudio_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd,
                unsigned long arg)
{
    int i = 0, val = 0, ret = 0; 
    struct Nvaudio_card *card =  (struct Nvaudio_card *)file->private_data;
    struct ac97_codec *codec  = card->ac97_codec[0];
    
    /* get the command */
    i = _IOC_NR(cmd);

    /* hack added to get the required vol for master and pcm*/
    if(_SIOC_DIR(cmd) == (_SIOC_WRITE|_SIOC_READ)) {
        get_user(val,(int *)arg);
        switch(i) {
            case SOUND_MIXER_VOLUME: /* Master volume */
                if(card->mastermute) return ret;
                mastervolright = (val >> 8) & 0xff;
                mastervolleft  = val & 0xff;
                if(mastervolright > 100) mastervolright = 100;
                if(mastervolleft  > 100) mastervolleft  = 100;
                break;

            case SOUND_MIXER_PCM: /* Pcm Vol */
                pcmvolright = (val >> 8) & 0xff;
                if(pcmvolright > 100) pcmvolright = 100;
                pcmvolleft  = val  & 0xff;
                if(pcmvolleft > 100) pcmvolleft = 100;
                break;
        }
    }

    if( cmd == SOUND_MIXER_PRIVATE1) {
     
        return   Nvaudio_private_ioctl(card, cmd, arg);
    }
    
    ret =  codec->mixer_ioctl(codec, cmd, arg);

    /* Set the volumes to other channel register based
       on channel support
    */
    switch(i) {
        case SOUND_MIXER_VOLUME: /* Master volume */
        case SOUND_MIXER_PCM:    /* Pcm Vol */
            Nvaudio_MultiChannel_volume(card, card->channels);
            break;
    }

    return ret;
}

struct file_operations Nvaudio_mixer_fops = {
    owner:      THIS_MODULE,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,9)
    llseek:     no_llseek,
#endif
    ioctl:      Nvaudio_ioctl_mixdev,
    open:       Nvaudio_open_mixdev,
};

/**********************************************************************
* Main Interrupt routine 
**********************************************************************/
static void Nvaudio_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
    u32 status = 0;
    struct Nvaudio_card *card = (struct Nvaudio_card *)dev_id;
    spin_lock(&card->lock);
    status = inl(card->iobase + GLOB_STA);

#ifdef DEBUG_INTERRUPTS
    printk("Nvaudio: Nvaudio_interrupt with GlobStatus 0x%x \n",status);
#endif

    if(!(status & INT_MASK)) {
        spin_unlock(&card->lock);
        return;  /* not for us */
    }

    while((status & INT_MASK)) {

        if(status & INT_PO) {
           Nvaudio_channel_pcminterrupt(card);
        }

#ifdef ENABLE_SPDIF
        if(status & INT_SP) {
            Nvaudio_channel_spdifinterrupt(card);
        }
#endif
        if(status & (INT_PI|INT_MC)){
            Nvaudio_channel_recinterrupt(card);
        }

        if(status & INT_GPI) {
        /* clear 'em */
            outl(status & INT_GPI, card->iobase + GLOB_STA);
        }

        if(status & INT_RCOM) {
        /* clear 'em */
            outl(status & INT_RCOM, card->iobase + GLOB_STA);
        }
        status = 0;
        status = inl(card->iobase + GLOB_STA);
    }
    spin_unlock(&card->lock);
}

/**********************************************************************
* AC97 codec initialisation.  These small functions exist so we don't
*  duplicate code between module init and apm resume 
**********************************************************************/
inline int Nvaudio_ac97_exists(struct Nvaudio_card *card,int ac97_number)
{
    u32 reg = inl(card->iobase + GLOB_STA);
    return (reg & (0x100 << ac97_number));
}

/**********************************************************************
* Nvaudio_ac97_enable_variable_rate
**********************************************************************/
inline int Nvaudio_ac97_enable_variable_rate(struct ac97_codec *codec)
{
    printk("Nvaudio: Nvaudio_ac97_enable_variable_rate \n");
    Nvaudio_ac97_set(codec, AC97_EXTENDED_STATUS, 9);
    Nvaudio_ac97_set(codec,AC97_EXTENDED_STATUS,
    Nvaudio_ac97_get(codec, AC97_EXTENDED_STATUS)|0xE800);
    return (Nvaudio_ac97_get(codec, AC97_EXTENDED_STATUS)&1);
}

/**********************************************************************
* probe & powerup codec
**********************************************************************/
int Nvaudio_ac97_probe_and_powerup(struct Nvaudio_card *card,struct ac97_codec *codec)
{
    /* Returns 0 on failure */
    int i;
    u16 id1 = 0, id2 = 0;
    if (ac97_probe_codec(codec) == 0) return 0;

    /* power it all up */
    Nvaudio_ac97_set(codec, AC97_POWER_CONTROL,
    Nvaudio_ac97_get(codec, AC97_POWER_CONTROL) & ~0x7f00);
    /* wait for analog ready */
    for (i=10;
         i && ((Nvaudio_ac97_get(codec, AC97_POWER_CONTROL) & 0xf) != 0xf);
         i--)  {

        set_current_state(TASK_UNINTERRUPTIBLE);
        schedule_timeout(HZ/20);
    }

    card->codecType = REALTEK_CODEC;
    /* Separate Checking for Sigmatel codec. If POWER_CONTROL registers are
       not getting set . check for the id and try to initialize the
       Sigmatel separately.
     */
    id1 = codec->codec_read(codec, AC97_VENDOR_ID1);
    id2 = codec->codec_read(codec, AC97_VENDOR_ID2);
    
    if((id1 == AC97REG_SIGMATEL_ID1) &&(id2 == AC97REG_SIGMATEL_ID2)){
        card->codecsec  = SIGMATEL_SECONDARY;
        card->codecType = SIGMATEL_CODEC;
    }

    if((id1 == AC97REG_ADI1885_ID1) && (id2 == AC97REG_ADI1885_ID2))    {
        card->codecType = ADI_CODEC;
        /* set the codec to use the rear surround slots */
        codec->codec_write(codec, AC97_ADI_MULTICHN, 0x7000);
    }

    if((i <= 0) && (card->codecsec == SIGMATEL_SECONDARY)){
        i = 10;
        /* set the codec to use enable the clock access register*/
        codec->codec_write(codec, AC97_SIGMATEL_CIC1, 0xABBA);
        /* set the codec to use the BIT_CLK*/
        codec->codec_write(codec, AC97_SIGMATEL_CIC2, 0x5400);
        /* enable the surround dac*/
        codec->codec_write(codec, AC97_EXTENDED_STATUS, 0x0000);
        /* set the codec to use the rear surround slots */
        codec->codec_write(codec, AC97_SIGMATEL_MULTICHN, 0x0001);
    }
    return i;
}

/**********************************************************************
* ac97 bus initialization
**********************************************************************/
int Nvaudio_ac97_random_init_stuff(struct Nvaudio_card *card)
{
    int i;
    u32 reg = inl(card->iobase + GLOB_CNT);

    if((reg&2)==0)  /* Cold required */
        reg|=2;
    else
        reg|=4; /* Warm */

    reg&=~8;    /* ACLink on */
    outl(reg , card->iobase + GLOB_CNT);

    for(i=0;i<10;i++) {
        if((inl(card->iobase+GLOB_CNT)&4)==0)
            break;

        set_current_state(TASK_UNINTERRUPTIBLE);
        schedule_timeout(HZ/20);
    }

    if(i==10)   {
        printk(KERN_ERR "Nvaudio: AC'97 reset failed.\n");
        return 0;
    }

    set_current_state(TASK_UNINTERRUPTIBLE);
    schedule_timeout(HZ/2);
    reg = inl(card->iobase + GLOB_STA);
    if(!(reg & 0x100))  {
        printk(KERN_INFO "Nvaudio: Codec not ready.. wait.. ");
        set_current_state(TASK_UNINTERRUPTIBLE);
        schedule_timeout(HZ);   /* actually 600mS by the spec */
        reg = inl(card->iobase + GLOB_STA);
        if(reg & 0x100)
            printk("OK\n");
        else
            printk("no response.\n");
    }

    /*read only if codec ready - otherwise it will hang the system */
    if(reg & 0x100) {
        inw(card->ac97base);
        return 1;
    }
    return 0;
}

/**********************************************************************
* function to initialize the codec 
**********************************************************************/
int __init Nvaudio_ac97_init(struct Nvaudio_card *card)
{
    int num_ac97       = 0;
    int total_channels = 0;
    struct ac97_codec *codec = 0;
    u16 eid = 0 , easc = 0;
    u32 reg = 0;

    card->codecType = 0;

    if(!Nvaudio_ac97_random_init_stuff(card)) return 0;

    /* Number of channels supported */
    /* What about the codec?  Just because the ICH supports */
    /* multiple channels doesn't mean the codec does.       */
    /* we'll have to modify this in the codec section below */
    /* to reflect what the codec has.                       */
    /* ICH and ICH0 only support 2 channels so don't bother */
    /* to check....                                         */

    card->channels = CHANNEL_STEREO;
    reg = inl(card->iobase + GLOB_STA);
    if ( reg & 0x0200000 )
        card->channels = 6;
    else if ( reg & 0x0100000 )
        card->channels = 4;
    printk(KERN_INFO "Nvaudio: Audio Controller supports %d channels.\n", card->channels);
    reg = inl(card->iobase + GLOB_CNT);
    outl(reg & 0xffcfffff, card->iobase + GLOB_CNT);

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

    inw(card->ac97base);

    for (num_ac97 = 0; num_ac97 < NR_AC97; num_ac97++) {

        /* Assume codec isn't available until we go through the
         * gauntlet below */
        card->ac97_codec[num_ac97] = NULL;

        /* The ICH programmer's reference says you should   */
        /* check the ready status before probing. So we chk */
        /* What do we do if it's not ready?  Wait and try */
        /* again, or abort?                               */
        if (!Nvaudio_ac97_exists(card,num_ac97)) {
            if(num_ac97 == 0)
                printk(KERN_ERR "Nvaudio: Primary codec not ready.\n");
            break; /* I think this works, if not ready stop */
        }

        if ((codec = kmalloc(sizeof(struct ac97_codec), GFP_KERNEL)) == NULL)
            return -ENOMEM;

        memset(codec, 0, sizeof(struct ac97_codec));

        /* initialize some basic codec information, other fields will be filled
           in ac97_probe_codec */
        codec->private_data = card;
        codec->id           = num_ac97;

        codec->codec_read   = Nvaudio_ac97_get;
        codec->codec_write  = Nvaudio_ac97_set;

        card->codecsec      = NO_SECONDARY;

        if(!Nvaudio_ac97_probe_and_powerup(card,codec)) {
            printk("Nvaudio: timed out waiting for codec %d analog ready", num_ac97);
            kfree(codec);
            break;  /* it didn't work */
        }
        /* Store state information about S/PDIF transmitter */
        card->ac97_status = 0;

        /* Don't attempt to get eid until powerup is complete */
        eid = Nvaudio_ac97_get(codec, AC97_EXTENDED_ID);

        if(eid==0xFFFF) {
            printk(KERN_WARNING "Nvaudio: no codec attached ?\n");
            kfree(codec);
            break;
        }

        codec->codec_write(codec, AC97_EXTENDED_MODEM_ID, 0L);

        /* As Spdif support only 48K - restrict the ac97 to play in 48K always*/
        /* added for nvaudio to do spdif support */
        /*if(spdif_status)
            eid &= ~1;
        */
        card->ac97_features = eid;

#ifdef NV_DEBUG_NORMAL
        printk("Nvaudio: EID  read from  codec %d first\n", eid);
#endif
        /* Now check the codec for useful features to make up for
           the dumbness of the hardware engine */

        if(!(eid&0x0001))
            printk(KERN_WARNING "Nvaudio: only 48Khz playback available.\n");
        else  {
            if(!Nvaudio_ac97_enable_variable_rate(codec)) {
                printk(KERN_WARNING "Nvaudio: Codec refused to allow VRA, using 48Khz only.\n");
                card->ac97_features&=~1;
            }
        }

        /* Turn on the amplifier */

        codec->codec_write(codec, AC97_POWER_CONTROL,
        codec->codec_read(codec, AC97_POWER_CONTROL) & ~0x8000);
        /* Determine how many channels the codec(s) support   */
        /*   - The primary codec always supports 2            */
        /*   - If the codec supports AMAP, surround DACs will */
        /*     automaticlly get assigned to slots.            */
        /*     * Check for surround DACs and increment if     */
        /*       found.                                       */
        /*   - Else check if the codec is revision 2.2        */
        /*     * If surround DACs exist, assign them to slots */
        /*       and increment channel count.                 */
        
        if((eid & 0xc000) == 0) /* primary codec */
            total_channels += 2;

        if(card->codecsec == SIGMATEL_SECONDARY)    {
            if((eid & 0x0080) != 0)  { //SDAC set
                total_channels += 4;
            }
            printk("Nvaudio: AC'97 codec %d, total channels = %d\n", num_ac97, total_channels);
        
        } else {
        
            if(eid & 0x200) { /* GOOD, AMAP support */
                if (eid & 0x0080) /* L/R Surround channels */
                    total_channels += 2;
                if (eid & 0x0140) /* LFE and Center channels */
                    total_channels += 2;
                printk("Nvaudio: AC'97 codec %d supports AMAP, total channels = %d\n", num_ac97, total_channels);

            } else if (card->codecType != ADI_CODEC) {  
                eid &= 0xffcf;
                if((eid & 0xc000) != 0) {
                    switch ( total_channels ) {
                        case 2:
                            /* Set dsa1, dsa0 to 01 */
                            eid |= 0x0010;
                            break;
                        case 4:
                            /* Set dsa1, dsa0 to 10 */
                            eid |= 0x0020;
                            break;
                        case 6:
                            /* Set dsa1, dsa0 to 11 */
                            eid |= 0x0030;
                            break;
                    }
                    total_channels += 2;
                }
                Nvaudio_ac97_set(codec, AC97_EXTENDED_ID, eid);
                eid = Nvaudio_ac97_get(codec, AC97_EXTENDED_ID);
                if (eid & 0x0080) /* L/R Surround channels */ {
                    total_channels += 2;
                    /* clear register 2a bit 12 */
                    easc = Nvaudio_ac97_get(codec,AC97_EXTENDED_STATUS);
                    easc &= 0xcfff;
                    Nvaudio_ac97_set(codec,AC97_EXTENDED_STATUS,easc);
                }
                if (eid & 0x0140) /* LFE and Center channels */ {
                    total_channels += 2;
                    /* clear register 2a bit 11 & 13 */
                    easc = Nvaudio_ac97_get(codec,AC97_EXTENDED_STATUS);
                    easc &= 0xb7ff;
                    Nvaudio_ac97_set(codec,AC97_EXTENDED_STATUS,easc);
                }
                printk("Nvaudio: AC'97 codec %d, DAC map configured, total channels = %d\n", num_ac97, total_channels);
            } 
        }
        if ((codec->dev_mixer = register_sound_mixer(&Nvaudio_mixer_fops, -1)) < 0) {
            printk(KERN_ERR "Nvaudio: couldn't register mixer!\n");
            kfree(codec);
            break;
        }

        card->ac97_codec[num_ac97] = codec;
        card->analogstate          = TRUE;
        card->digitalstate         = TRUE;


        if(card->codecsec == NO_SECONDARY) {
            /* Get the master vol and pcmvol for spdif - need to take separate for multiple codec */
            mastervolleft = ( codec->read_mixer(codec,SOUND_MIXER_VOLUME)  >> 8 ) & 0xff;
            if(mastervolleft > 100) mastervolleft = 100;
            mastervolright =  codec->read_mixer(codec,SOUND_MIXER_VOLUME) & 0xff;
            if(mastervolright > 100) mastervolright = 100;


            pcmvolleft = ( codec->read_mixer(codec,SOUND_MIXER_PCM)  >> 8 ) & 0xff;
            if(pcmvolleft > 100) pcmvolleft = 100;
            pcmvolright = codec->read_mixer(codec,SOUND_MIXER_PCM) & 0xff;
            if(pcmvolright > 100) pcmvolright = 100;
        }

        if(card->codecType == ADI_CODEC) {
            total_channels += 4;
            num_ac97       += 1;
            break;
        }

    }

    /*Allocate the List for Output buffer */
    if(!card->outList) {
        card->outList = (listPtr) kmalloc(sizeof(struct LList), GFP_KERNEL);
        if(card->outList == NULL) {
            printk(KERN_ERR " Failed to allocate the outbuffer Linked List \n");
            return -ENOMEM;
        }
        memset(card->outList,0,sizeof(struct LList));
    }
    /* pick the minimum of channels supported by ICHx or codec(s) */
    card->channels = (card->channels > total_channels)?total_channels:card->channels;
    /* set the volume registers to maximum*/
    if((card->channels == 6) && (card->codecType == REALTEK_CODEC)) {
        if(codec) {
            Nvaudio_ac97_set(codec,AC97_SURROUND_MASTER,0);
            Nvaudio_ac97_set(codec,AC97_CENTER_LFE_MASTER,0);
        }
    }
    return num_ac97;
}

/**********************************************************************
* function to configure clocks
**********************************************************************/
void __init Nvaudio_configure_clocking (void)
{
    struct Nvaudio_card   *card  = NULL;
    struct Nvaudio_state  *state = NULL;
    struct dmabuf *dmabuf;
    unsigned int i, offset, new_offset;
    unsigned long flags = 0;

    card = devs;
    /* We could try to set the clocking for multiple cards, but can you even have
     * more than one Nvaudio in a machine?  Besides, clocking is global, so unless
     * someone actually thinks more than one Nvaudio in a machine is possible and
     * decides to rewrite that little bit, setting the rate for more than one card
     * is a waste of time.
     */
    if(card != NULL) {
        state =  (struct Nvaudio_state *)
                    kmalloc(sizeof(struct Nvaudio_state), GFP_KERNEL);

        if (state == NULL)
            return;

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

#ifdef NV_DEBUG_NORMAL
    printk("Nvaudio:Nvaudio_configure_clocking with card %p and state %p dma %p \n",card,state,dmabuf);
#endif

        state->card    = card;
        card->wavout   = state;
        spin_lock_init(&state->lock);
        init_waitqueue_head(&dmabuf->wait);
        state->fmt     = NVAUDIO_FMT_STEREO | NVAUDIO_FMT_16BIT;
        state->trigger = PCM_ENABLE_OUTPUT;
        Nvaudio_set_dac_rate(state, 48000);
        if(prog_dmabuf(state,0) != 0) {
            goto config_out_nodmabuf;
        }

        if(dmabuf->dmasize < 16384) {
            goto config_out;
        }

        dmabuf->count = dmabuf->dmasize;
        stop_dac(state);
        outb(31,card->iobase+PO_LVI);
        save_flags(flags);
        cli();
        start_dac(state);
        offset = Nvaudio_get_dma_addr(state);
        mdelay(50);
        new_offset = Nvaudio_get_dma_addr(state);
        stop_dac(state);
        restore_flags(flags);
        i = new_offset - offset;

#ifdef NV_DEBUG_NORMAL
        printk("Nvaudio: %d bytes in 50 milliseconds\n", i);
#endif
        if(i == 0)
            goto config_out;
        i = i / 4 * 20;
        if (i > 48500 || i < 47500) {
            clocking = clocking * clocking / i;
            printk("Nvaudio: setting clocking to %d\n", clocking);
        }
        card->mastermute = 0;
config_out:
        dealloc_dmabuf(state);
config_out_nodmabuf:
        kfree(state);
        card->wavout = NULL;
    }
}

/**********************************************************************
* install the driver, we do not allocate hardware channel nor DMA buffer now, 
* they are defered until "ACCESS" time 
* (in prog_dmabuf called by open/read/write/ioctl/mmap) 
**********************************************************************/
int __init Nvaudio_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id)
{
#ifdef ENABLE_SPDIF
    u32 regvalue = 0;
#endif
    struct Nvaudio_card *card;

    if (pci_enable_device(pci_dev))
        return -EIO;

    if (pci_set_dma_mask(pci_dev, NVAUDIO_DMA_MASK)) {
        printk(KERN_ERR "Nvidia: architecture does not support"
               " 32bit PCI busmaster DMA\n");
        return -ENODEV;
    }

    if ((card = kmalloc(sizeof(struct Nvaudio_card), GFP_KERNEL)) == NULL) {
        printk(KERN_ERR "Nvaudio: out of memory\n");
        return -ENOMEM;
    }
    memset(card, 0, sizeof(*card));
    spin_lock_init(&card->lock);

    card->initializing = 1;
    card->iobase       = pci_resource_start (pci_dev, 1);
    card->ac97base     = pci_resource_start (pci_dev, 0);
    card->pci_dev      = pci_dev;
    card->pci_id       = pci_id->device;
    card->irq          = pci_dev->irq;
    card->next         = devs;
    card->magic        = NVAUDIO_CARD_MAGIC;
#ifdef CONFIG_PM
    card->pm_suspended=0;
#endif
    spin_lock_init(&card->lock);
    devs            = card;
    pci_set_master(pci_dev);

    printk(KERN_INFO "Nvaudio: %s found at IO 0x%04lx and 0x%04lx, IRQ %d\n",
           card_names[pci_id->driver_data], card->iobase, card->ac97base,
           card->irq);

#ifdef ENABLE_SPDIF
    pci_read_config_dword(pci_dev,0x4C,&regvalue);
    regvalue = (regvalue & 0xFEFFFFFF) |  0x1000000;
    pci_write_config_dword(pci_dev,0x4C,regvalue);
#endif

    if ((card->channel = pci_alloc_consistent(pci_dev,
        sizeof(struct Nvaudio_channel)*NR_HW_CH, &card->chandma)) == NULL) {
        printk(KERN_ERR "Nvaudio: cannot allocate channel DMA memory\n");
        goto out_mem;
    }

    /* claim our iospace and irq */
    request_region(card->iobase, 64, card_names[pci_id->driver_data]);
    request_region(card->ac97base, 256, card_names[pci_id->driver_data]);

    if (request_irq(card->irq, &Nvaudio_interrupt, SA_SHIRQ,
            card_names[pci_id->driver_data], card)) {
        printk(KERN_ERR "Nvaudio: unable to allocate irq %d\n", card->irq);
        goto out_pio;
    }

    /* initialize AC97 codec and register /dev/mixer */
    if (Nvaudio_ac97_init(card) <= 0) {
        free_irq(card->irq, card);
        goto out_pio;
    }
    pci_set_drvdata(pci_dev, card);

    if(clocking == 0) {
        clocking = 48000;
        Nvaudio_configure_clocking();
    }

    /* register /dev/dsp */
    if ((card->dev_audio = register_sound_dsp(&Nvaudio_fops, -1)) < 0) {
        int i;
        printk(KERN_ERR "Nvaudio: couldn't register DSP device!\n");
        free_irq(card->irq, card);
        for (i = 0; i < NR_AC97; i++)
        if (card->ac97_codec[i] != NULL) {
            unregister_sound_mixer(card->ac97_codec[i]->dev_mixer);
            kfree (card->ac97_codec[i]);
        }
        goto out_pio;
    }
    card->initializing = 0;
    /* set the Spkr as Stereo by default*/
    card->spkrselect  = NV_SPKR_STEREO;
    card->curspkr     = card->spkrselect;
    return 0;

out_pio:
    release_region(card->iobase, 64);
    release_region(card->ac97base, 256);
    pci_free_consistent(pci_dev, sizeof(struct Nvaudio_channel)*NR_HW_CH,
        card->channel, card->chandma);
out_mem:
    kfree(card);
    printk(KERN_ERR "Nvaudio: ERROR DONE CARD FREED \n");
    return -ENODEV;

}

/**********************************************************************
* function to remove the device
**********************************************************************/
void __devexit Nvaudio_remove(struct pci_dev *pci_dev)
{
    int i;
    u32 regvalue = 0;
    struct Nvaudio_card *card = pci_get_drvdata(pci_dev);

    /* stop the spdif Interrupt stuff */
    pci_read_config_dword(pci_dev,0x4C,&regvalue);
    regvalue = (regvalue & 0xFEFFFFFF);
    pci_write_config_dword(pci_dev,0x4C,regvalue);

    if(card->outList){
        /* delete any node if present */
        while(card->outList->nodecount){
            Nvaudio_deletenode(card);
        }
        kfree(card->outList);
        card->outList = NULL;
    }
    /* free hardware resources */
    free_irq(card->irq, devs);
    release_region(card->iobase, 64);
    release_region(card->ac97base, 256);

    /* unregister audio devices */
    for (i = 0; i < NR_AC97; i++)
        if (card->ac97_codec[i] != NULL) {
            unregister_sound_mixer(card->ac97_codec[i]->dev_mixer);
            kfree (card->ac97_codec[i]);
            card->ac97_codec[i] = NULL;
        }
    unregister_sound_dsp(card->dev_audio);
    kfree(card);
#ifdef NV_DEBUG_NORMAL
    printk("Nvaudio: in Function Nvaudio_remove \n");
#endif

}

/**********************************************************************
* function to pm suspend
**********************************************************************/
#ifdef CONFIG_PM
int Nvaudio_pm_suspend(struct pci_dev *dev, u32 pm_state)
{
    struct Nvaudio_card *card = pci_get_drvdata(dev);
    struct Nvaudio_state *state = NULL;
    unsigned long flags;
    struct dmabuf *dmabuf = NULL;
    int i = 0,num_ac97;
#ifdef NV_DEBUG_NORMAL
    printk("Nvaudio: Nvaudio_pm_suspend called\n");
#endif
    if(!card) return 0;
    card->pm_suspended=1;
    do {
        switch(i) {
            case 0:
                state = card->wavout;
                if(!state) break;
                dmabuf = &state->dmabuffer;
                break;
            case 1:
                state = card->wavin;
                if(!state) break;
                dmabuf = &state->dmabuffer;
                break;
            case 2:
                state = card->spout;
                if(!state) break;
                dmabuf = &state->dmabuffer;
                break;
        }
        i = i + 1 ;
        if(!dmabuf) continue;
        spin_lock_irqsave(&state->lock, flags);
        if(state->enable & DAC_RUNNING ||
           (dmabuf->count && (state->trigger & PCM_ENABLE_OUTPUT))) {
            state->pm_saved_dac_rate = state->rate;
            stop_dac(state);
        } else {
            state->pm_saved_dac_rate=0;
        }
        if(state->enable & ADC_RUNNING) {
            state->pm_saved_adc_rate = state->rate;
            stop_adc(state);
        } else {
            state->pm_saved_adc_rate=0;
        }
        if(state->enable & SPDIF_RUNNING) {
            state->pm_saved_spdif_rate = state->rate;
            stop_spdif(state);
        } else {
            state->pm_saved_spdif_rate=0;
        }
        state->ready = 0;
        dmabuf->swptr = dmabuf->hwptr = 0;
        dmabuf->count = dmabuf->total_bytes = 0;
        spin_unlock_irqrestore(&state->lock, flags);
        dmabuf = NULL;
        state  = NULL;
    }while(i < 3);

    /* save mixer settings */
    for (num_ac97 = 0; num_ac97 < NR_AC97; num_ac97++) {
        struct ac97_codec *codec = card->ac97_codec[num_ac97];
        if(!codec) continue;
        for(i=0;i< SOUND_MIXER_NRDEVICES ;i++) {
            if((supported_mixer(codec,i)) &&
               (codec->read_mixer)) {
                card->pm_saved_mixer_settings[i][num_ac97]=
                    codec->read_mixer(codec,i);
            }
        }
    }
    pci_save_state(dev,card->pm_save_state); /* XXX do we need this? */
    pci_disable_device(dev); /* disable busmastering */
    pci_set_power_state(dev,3); /* Zzz. */

    return 0;
}

/**********************************************************************
* function to pm resume
* Function that retains the hardware registers when coming from sleep mode
**********************************************************************/
int Nvaudio_pm_resume(struct pci_dev *dev)
{
    int num_ac97,i=0;
    struct Nvaudio_card *card=pci_get_drvdata(dev);
    pci_enable_device(dev);
    pci_restore_state (dev,card->pm_save_state);

    Nvaudio_ac97_random_init_stuff(card);

    for (num_ac97 = 0; num_ac97 < NR_AC97; num_ac97++) {
        struct ac97_codec *codec = card->ac97_codec[num_ac97];
        /* check they haven't stolen the hardware while we were
           away */
        if(!codec || !Nvaudio_ac97_exists(card,num_ac97)) {
            if(num_ac97) continue;
            else BUG();
        }
        if(!Nvaudio_ac97_probe_and_powerup(card,codec)) BUG();

        if((card->ac97_features&0x0001)) {
            /* at probe time we found we could do variable
               rates, but APM suspend has made it forget
               its magical powers */
            if(!Nvaudio_ac97_enable_variable_rate(codec)) BUG();
        }
        /* we lost our mixer settings, so restore them */
        for(i=0;i< SOUND_MIXER_NRDEVICES ;i++) {
            if(supported_mixer(codec,i)){
                int val=card->
                    pm_saved_mixer_settings[i][num_ac97];
                codec->mixer_state[i]=val;
                codec->write_mixer(codec,i,
                           (val  & 0xff) ,
                           ((val >> 8)  & 0xff) );
                /* set the volumes for spdif control */
                if(i == SOUND_MIXER_VOLUME) { /* Master volume */
                    mastervolright = (val >> 8) & 0xff;
                    if(mastervolright > 100) mastervolright = 100;
                    mastervolleft = val & 0xff;
                    if(mastervolleft > 100) mastervolleft = 100;
                }
                if(i == SOUND_MIXER_PCM) { /* Pcm Vol */
                    pcmvolright = (val >> 8) & 0xff;
                    if(pcmvolright > 100) pcmvolright = 100;
                    pcmvolleft = val  & 0xff;
                    if(pcmvolleft > 100) pcmvolleft = 100;
                }
            }
        }
    }

    /* we need to restore the sample rate from whatever it was */
    if(card->wavout) {
        if(card->wavout->pm_saved_dac_rate)
            Nvaudio_set_dac_rate(card->wavout,card->wavout->pm_saved_dac_rate);
    }
    if(card->wavin) {
        if(card->wavin->pm_saved_adc_rate)
            Nvaudio_set_adc_rate(card->wavin,card->wavin->pm_saved_adc_rate);
    }
    if(card->spout) {
        if(card->spout->pm_saved_spdif_rate)
            Nvaudio_set_spdif_rate(card->spout,card->spout->pm_saved_spdif_rate);
    }

    card->pm_suspended = 0;

    /* any processes that were reading/writing during the suspend
       probably ended up here */
    if(card->wavout){
       wake_up(&card->wavout->dmabuffer.wait);
    }

    if(card->wavin){
       wake_up(&card->wavin->dmabuffer.wait);
    }

    if(card->spout){
       wake_up(&card->spout->dmabuffer.wait);
    }

    return 0;
}
#endif /* CONFIG_PM */

MODULE_AUTHOR("");
MODULE_DESCRIPTION("Nvidia audio support");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,9)
MODULE_LICENSE("GPL");
#endif
MODULE_PARM(clocking, "i");
MODULE_PARM(spdif_status, "i");

static struct pci_driver Nvaudio_pci_driver = {
    name:       Nvaudio_MODULE_NAME,
    id_table:   Nvaudio_pci_tbl,
    probe:      Nvaudio_probe,
    remove:     __devexit(Nvaudio_remove),
#ifdef CONFIG_PM
    suspend:    Nvaudio_pm_suspend,
    resume:     Nvaudio_pm_resume,
#endif /* CONFIG_PM */
};

/**********************************************************************
* init module function
**********************************************************************/
int __init Nvaudio_init_module (void)
{
    if (!pci_present())   /* No PCI bus in this machine! */
        return -ENODEV;

    printk(KERN_INFO "Nvidia Audio, " __TIME__ " " __DATE__ " version %s \n",
           DRIVERVER);

    printk("Nvaudio: in Funcction Nvaudio_init_module \n");
    if (!pci_register_driver(&Nvaudio_pci_driver)) {
        pci_unregister_driver(&Nvaudio_pci_driver);
                return -ENODEV;
    }
    return 0;
}

/**********************************************************************
* Module clean function
**********************************************************************/
void __exit Nvaudio_cleanup_module (void)
{
    pci_unregister_driver(&Nvaudio_pci_driver);
}

module_init(Nvaudio_init_module);
module_exit(Nvaudio_cleanup_module);

/*
Local Variables:
c-basic-offset: 8
End:
*/
