/*****************************************************************************
 Copyright (c) 2001-2004, Intel Corporation 

 All rights reserved.

 Redistribution and use in source and binary forms, with or without 
 modification, are permitted provided that the following conditions are met:

 1. Redistributions of source code must retain the above copyright notice, 
 this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice,
 this list of conditions and the following disclaimer in the documentation 
 and/or other materials provided with the distribution.

 3. Neither the name of Intel Corporation nor the names of its contributors 
 may be used to endorse or promote products derived from this software 
 without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 

****************************************************************************/

#include "afedsp_int.h"
#include "softcore.h"
#include "lock_lin.h"

static void dsp_function(unsigned long x);
void sound_function(void *x);
DECLARE_TASKLET(dsp_tasklet, dsp_function, 0);
DECLARE_WAIT_QUEUE_HEAD(sound_wait);
struct file_operations proc_afe_operations;
struct proc_dir_entry *afeproc;
volatile unsigned char line_io_enabled=0;


void RTS_Task(void *ptr);
asmlinkage void linux_schedule(void)
{
schedule();
}
extern void Precdialer_Task (void *t);
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,0) 
struct tq_struct  precdialer_task =
{
   routine: (void (*) (void*)) Precdialer_Task,
   data: NULL
};
DECLARE_TASKLET(sound_tasklet, sound_function, 0);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
DECLARE_WORK(prec_wq, (work_func_t)Precdialer_Task);
DECLARE_WORK(sound_wq, (work_func_t)sound_function);
#else
DECLARE_WORK(prec_wq, Precdialer_Task, 0);
DECLARE_WORK(sound_wq, sound_function, 0);
#endif

asmlinkage void schedule_precdialer(void)
{
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,0) 
      schedule_task(&precdialer_task);
#else
//      tasklet_schedule(&rs_tasklet);
      schedule_work(&prec_wq);
#endif
}

asmlinkage void *linux_kmalloc(unsigned int Sz)
{
    return kmalloc(Sz, GFP_KERNEL);
}

asmlinkage void linux_kfree(void *p_)
{
    if (p_) kfree(p_);
}

asmlinkage void *linux_memset(void *s, int c, unsigned long n)
{
    return memset(s, c, (size_t)n);
}

asmlinkage void *linux_memcpy(void *s, const void *ct, unsigned long n)
{
    return memcpy(s, ct, (size_t)n);
}

asmlinkage void linux_udelay(unsigned long usecs)
{
    udelay(usecs);
}

asmlinkage long long linux_rdtscll(void)
{
    long long t;
    rdtscll(t);
    return t;
}

asmlinkage void linux_gettimeofday(struct timeval *tv)
{
    do_gettimeofday(tv);
}

// Used from DSP code, might be called from interrput, so GFP_ATOMIC
asmlinkage void *malloc(unsigned int Sz)
{
    return kmalloc(Sz, GFP_ATOMIC);
}

asmlinkage void free(void *p_)
{
    if (p_) kfree(p_);
}

asmlinkage void KdPrintf(const char *fmt, ...)
{
    char buf[512];
    va_list ap;
    va_start(ap, fmt);
    vsprintf(buf, fmt, ap);
    va_end(ap);
#if defined (DEBUG_LINUX)
    printk(KERN_INFO"%s", buf);
#endif
}

asmlinkage void DbgPrint(const char *fmt, ...)
{
KdPrintf(fmt);
}
asmlinkage int AllocateDmaResources (int dma_size)
{
    michnl->dma_size = dma_size;
    michnl->bdbar = pci_alloc_consistent(
                        devAfe->pcidev,
                        michnl->dma_size,
                        &michnl->bdbar_addr);
    if (michnl->bdbar == NULL) {
        return -ENOMEM;
    }
    memset(michnl->bdbar, 0, dma_size);

    mochnl->dma_size = dma_size;
    mochnl->bdbar = pci_alloc_consistent(
                        devAfe->pcidev,
                        mochnl->dma_size,
                        &mochnl->bdbar_addr);
    if (mochnl->bdbar == NULL) {
        return -ENOMEM;
    }
    memset(mochnl->bdbar, 0, dma_size);

    return 0;
}

asmlinkage void release_resources (afe_device_t *dev, int level)
{
    switch (level) {
        case 1:
            kfree(dev);
            break;
        case 2:
            if (sound_enabled) {
                remove_afeproc();
                kfree((void *)dev->SysAudio.SoundBuffer);
            }
            kfree(dev->afeInternal);
            kfree(dev);			
            break;
        default :
            printk(KERN_ERR"%s: Error in release_resources\n", DRIVER_NAME);
    }
}

/************************************************************************
Function name:  modem_init
Description:    Main funktions for initializing the AFE and Hardware. it 
				also includes allocation of buffers
Inputs:         
Outputs:        Status Err or Ok
Notes:          Should be called from init_module
**************************************************************************
Date      | Author          | Change  
**************************************************************************
1.06.2002 |Alex Komarov     | Initial version
**************************************************************************/
int modem_init (void *data)
{
    int Status = 0;                   // Status information about modem initialization
    struct softcore_struct *driver = (struct softcore_struct *)data;

//---------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------
    devAfe->pcidev   = driver->pci.pdev;
    devAfe->afe_type = driver->afe_type;
    switch (devAfe->afe_type)
    {
        case SELAH_PCI_VENDOR_ID + 1:
        case SI3052_VENDOR_ID + 2:
            devAfe->iobase = (unsigned int)ioremap_nocache(
                        driver->io_base,
                        driver->io_range);
            break;
        case TJ320_VENDOR_ID:
            devAfe->iobase = (unsigned int)ioremap_nocache(
                        driver->io_base,
                        driver->io_range);
            break;
        case INTEL_VENDOR_ID:
        case SIS_VENDOR_ID:
        case NVIDIA_VENDOR_ID:
        case TJ320_VENDOR_ID + 1:
            devAfe->iobase = (unsigned int)ioremap_nocache(driver->io_base,
                                                           driver->io_range);
            devAfe->membase = driver->mem_base;
//            request_region(driver->io_base, driver->io_range, DRIVER_NAME);
            request_region(driver->mem_base, driver->mem_range, DRIVER_NAME);
            break;
        case ALI_VENDOR_ID:
        case VIA_VENDOR_ID:
            devAfe->iobase = driver->io_base;
            request_region(driver->io_base, driver->io_range, DRIVER_NAME);
            break;
        case ATI_VENDOR_ID:
            devAfe->iobase = (unsigned int)ioremap_nocache(
                        driver->io_base,
                        driver->io_range);
            break;
        default:
            printk(KERN_ERR"Unknown device, exiting\n");
            return -1;
    }

// Afe&DSP initialization ----------------------------------------------------------------
	{
		unsigned int device_ids[3];
		device_ids[0] = driver->pci.pdev->device;
		device_ids[1] = driver->pci.pdev->vendor;
		device_ids[2] = driver->pci.pdev->subsystem_device << 16 | driver->pci.pdev->subsystem_vendor;
		Status = afe_init(device_ids);
	    if (0 != Status )
        {
            printk(KERN_ERR"Can't initialize modem h/w\n");
		    release_resources((void*)devAfe, 2);
            return -1;
        }
	}

    printk(KERN_INFO "%s: Loaded\n", DRIVER_NAME);

//    calibrate_rdtsc();
    return Status;
}

int sound_init(void)
{
// memory allocation block
    devAfe = (afe_device_t *)kmalloc(sizeof(afe_device_t), GFP_KERNEL);
    if (!devAfe) {
        printk(KERN_ERR "%s: Failed to init\n", DRIVER_NAME);
        return -ENOMEM;
    }
    memset((void*)devAfe, 0, sizeof(afe_device_t));
    if (sound_enabled) {
        devAfe->SysAudio.SoundBuffer = kmalloc(1024, GFP_ATOMIC | GFP_DMA);
        if (devAfe->SysAudio.SoundBuffer == NULL) {
            printk(KERN_ERR"Can't allocate memory\n");
	        return ENOMEM;
        }
        create_afeproc();
	    printk(KERN_INFO"sound enabled\n");
    }
    else
    {
	    printk(KERN_INFO"sound disabled\n");
    }
    return 0;
}

/************************************************************************
Function name:  modem_kill
Description:    Cleans AFE and Layer 1 before removing module it 
				also includes allocation of buffers
Inputs:         
Outputs:        
Notes:          Should be called from cleanup_module(RemoveDevice analgue)
**************************************************************************
Date      | Author          | Change  
**************************************************************************
1.06.2002 |Alex Komarov     | Initial version
**************************************************************************/
int modem_kill (void)
{
    ModemCardStop();
#if !defined(AFE_S1724)
    if (michnl->bdbar != NULL)    
        pci_free_consistent(devAfe->pcidev, michnl->dma_size, michnl->bdbar,
                             michnl->bdbar_addr);
    if (mochnl->bdbar != NULL)    
        pci_free_consistent(devAfe->pcidev, mochnl->dma_size, mochnl->bdbar,
                             michnl->bdbar_addr);
#endif
    afe_close();

    if (devAfe)
        switch (devAfe->afe_type)
        {
            case SI3052_VENDOR_ID + 2:
            case TJ320_VENDOR_ID:
                iounmap((unsigned int*)devAfe->iobase);
            break;
            default:
            break;
        }
    return 0;
}

static void mt_function(unsigned long x)
{
    ModemTask(pDspResources);
}

static void dsp_function(unsigned long x)
{
    static unsigned long cr0;
    static unsigned long linux_fpe[27];

    MT_AcquireLock_mt_dsp();
    save_cr0_and_clts(cr0);
    save_fpenv(linux_fpe);    

    DspTask(0);

    restore_fpenv(linux_fpe); 
    restore_cr0(cr0);
    MT_ReleaseLock_mt_dsp();
}

void sound_function(void *x)
{
   SoundTask((void*)&devAfe->SysAudio);
}

asmlinkage int afe_irq(void)
{
    if (sound_enabled) {
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,0) 
       tasklet_schedule(&sound_tasklet);
#else
      schedule_work(&sound_wq);
#endif
    }

    tasklet_schedule(&dsp_tasklet);
    return 0;
}

// Layer 1 to process packet from Layer 2
asmlinkage void InterruptDSP(void *unused)
{
    MT_AcquireLock_mt_dsp();
    mt_function(0);
    MT_ReleaseLock_mt_dsp();
}

static int soundSem = 0;
asmlinkage int wake_up_sound(void)
{
    wake_up_interruptible(&sound_wait);
    soundSem = 0;
    return 0;
}

int create_afeproc(void)
{
    afeproc = create_proc_entry("afe", S_IFREG|S_IRUGO, NULL);
    if(afeproc == NULL)
        return -ENOMEM;
    afeproc->proc_fops = &proc_afe_operations;
    return 0;
}

void remove_afeproc(void)
{
    remove_proc_entry("afe", NULL);
}

static int use_count = 0;
static int afe_Open(struct inode * inode, struct file * file)
{

    if (use_count)
        return -1;
    use_count++;
    soundSem = 1;
    wait_event_interruptible(sound_wait, soundSem == 0);

    return 0;
}

static int afe_Release(struct inode * inode, struct file * file)
{
    use_count--;
    return 0;
}

static ssize_t afe_Write(struct file * file, const char * buf,
			size_t count, loff_t *ppos)
{
    char tmpbuf[512];
    if (file->f_flags & O_NONBLOCK) {
        return -EAGAIN;
    }
    if (copy_from_user(tmpbuf, buf, count))
        return 0;
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,5,0) 
    circular_buffer_put(write_buffer, tmpbuf, count);
#endif
    return count;
}

static ssize_t afe_Read(struct file *file, char *buf,
			size_t count, loff_t *ppos)
{
    smpl_p sm = (smpl_p)buf;
    if (file->f_flags & O_NONBLOCK) {
        return -EAGAIN;
    }
    devAfe->SysAudio.wakeUpSound = 0;
    soundSem = 1;
    wait_event_interruptible(sound_wait, soundSem == 0);
    sm->sRate   = devAfe->SysAudio.RenderSamplesPerSec;
    sm->sVol    = devAfe->SysAudio.RenderVolumeLevel;
    sm->sBufLen = 96*2; // FIXME AK
    if(copy_to_user((void*)&sm->sBuf, (void*)devAfe->SysAudio.SoundBuffer, sm->sBufLen + sizeof(smpl_t)-1))
      return -EFAULT;
    
    return sm->sBufLen + sizeof(smpl_t) - 1; 
}	

static unsigned int afe_Poll(struct file * file, poll_table * wait)
{
    unsigned int mask = 0;

    poll_wait(file, &sound_wait, wait); // Wait until we have some data. This blocking behavior is unsuitable
                                        // for poll, but this call has the only client, so I can stand the consequences.
    mask |= POLLIN;

    return mask;
}
struct file_operations proc_afe_operations = {
    write:	 afe_Write,
    read:	 afe_Read,
    poll:	 afe_Poll,
    open:	 afe_Open,
    release: afe_Release,
};


